fix: apply cargo fmt to resolve CI formatting failures
Some checks failed
CI/CD Pipeline / Code Format (push) Successful in 4s
CI/CD Pipeline / Clippy Lints (push) Failing after 44s
CI/CD Pipeline / Enrollment Tests (push) Has been skipped
CI/CD Pipeline / Verify Enrollment CLI Flag (push) Has been skipped
CI/CD Pipeline / All Unit Tests (push) Successful in 1m15s
CI/CD Pipeline / Build Debian Package (push) Has been skipped
CI/CD Pipeline / Build Debian Package (Ubuntu 22.04) (push) Has been skipped
CI/CD Pipeline / Build RPM Package (push) Has been skipped
CI/CD Pipeline / Build Alpine Package (push) Has been skipped
CI/CD Pipeline / Build Arch Package (push) Has been skipped
CI/CD Pipeline / Security Audit (push) Successful in 4s
Some checks failed
CI/CD Pipeline / Code Format (push) Successful in 4s
CI/CD Pipeline / Clippy Lints (push) Failing after 44s
CI/CD Pipeline / Enrollment Tests (push) Has been skipped
CI/CD Pipeline / Verify Enrollment CLI Flag (push) Has been skipped
CI/CD Pipeline / All Unit Tests (push) Successful in 1m15s
CI/CD Pipeline / Build Debian Package (push) Has been skipped
CI/CD Pipeline / Build Debian Package (Ubuntu 22.04) (push) Has been skipped
CI/CD Pipeline / Build RPM Package (push) Has been skipped
CI/CD Pipeline / Build Alpine Package (push) Has been skipped
CI/CD Pipeline / Build Arch Package (push) Has been skipped
CI/CD Pipeline / Security Audit (push) Successful in 4s
Format all enrollment module source files and tests per rustfmt standards. Resolves Gitea CI workflow cargo fmt check failures.
This commit is contained in:
@ -25,8 +25,8 @@ use std::os::unix::fs::PermissionsExt;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tempfile::TempDir;
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
use wiremock::matchers::{method, path, path_regex};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
/// Test constants
|
||||
const TEST_TOKEN: &str = "test_enrollment_token";
|
||||
@ -63,10 +63,7 @@ fn create_temp_dirs() -> (TempDir, TempDir) {
|
||||
/// Initialize an empty whitelist YAML file at the given path.
|
||||
/// Required because WhitelistManager::new() loads existing config on construction.
|
||||
fn init_empty_whitelist(path: &str) {
|
||||
std::fs::write(
|
||||
path,
|
||||
"entries: []\n",
|
||||
).expect("Failed to create initial whitelist file");
|
||||
std::fs::write(path, "entries: []\n").expect("Failed to create initial whitelist file");
|
||||
}
|
||||
|
||||
/// Build a TLS config pointing to the temp certificate directory.
|
||||
@ -76,7 +73,10 @@ fn build_tls_config(cert_dir: &std::path::Path) -> TlsConfig {
|
||||
port: 12443,
|
||||
ca_cert: cert_dir.join("ca.pem").to_string_lossy().to_string(),
|
||||
server_cert: cert_dir.join("server.pem").to_string_lossy().to_string(),
|
||||
server_key: cert_dir.join("server.key.pem").to_string_lossy().to_string(),
|
||||
server_key: cert_dir
|
||||
.join("server.key.pem")
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
min_tls_version: "1.3".to_string(),
|
||||
}
|
||||
}
|
||||
@ -104,7 +104,11 @@ async fn test_full_enrollment_flow_happy_path() {
|
||||
let ca_cert_path = cert_dir.path().join("ca.pem");
|
||||
let server_cert_path = cert_dir.path().join("server.pem");
|
||||
let server_key_path = cert_dir.path().join("server.key.pem");
|
||||
let whitelist_path = whitelist_dir.path().join("whitelist.yaml").to_string_lossy().to_string();
|
||||
let whitelist_path = whitelist_dir
|
||||
.path()
|
||||
.join("whitelist.yaml")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
init_empty_whitelist(&whitelist_path);
|
||||
|
||||
@ -128,8 +132,7 @@ async fn test_full_enrollment_flow_happy_path() {
|
||||
let count = poll_count_clone.fetch_add(1, Ordering::SeqCst);
|
||||
if count < 1 {
|
||||
// First poll returns pending (simulates admin review delay)
|
||||
ResponseTemplate::new(200)
|
||||
.set_body_string(r#"{"status": "pending"}"#)
|
||||
ResponseTemplate::new(200).set_body_string(r#"{"status": "pending"}"#)
|
||||
} else {
|
||||
// Second poll returns approved with full PKI bundle
|
||||
ResponseTemplate::new(200).set_body_string(&format!(
|
||||
@ -152,7 +155,10 @@ async fn test_full_enrollment_flow_happy_path() {
|
||||
let client = build_client(&base_url);
|
||||
|
||||
// Phase 1: Registration
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
assert_eq!(response.polling_token, TEST_TOKEN);
|
||||
|
||||
// Phase 2: Polling (should get pending first, then approved)
|
||||
@ -172,10 +178,15 @@ async fn test_full_enrollment_flow_happy_path() {
|
||||
&bundle.server_crt,
|
||||
&bundle.server_key,
|
||||
Some(&tls_config),
|
||||
).await.expect("PKI provisioning should succeed");
|
||||
)
|
||||
.await
|
||||
.expect("PKI provisioning should succeed");
|
||||
|
||||
// Phase 3b: Whitelist update (manager_ip for localhost URL returns 127.0.0.1)
|
||||
let manager_ip = client.manager_ip().await.expect("Should resolve manager IP");
|
||||
let manager_ip = client
|
||||
.manager_ip()
|
||||
.await
|
||||
.expect("Should resolve manager IP");
|
||||
provision::append_manager_to_whitelist(&manager_ip, &whitelist_path)
|
||||
.await
|
||||
.expect("Whitelist append should succeed");
|
||||
@ -186,14 +197,29 @@ async fn test_full_enrollment_flow_happy_path() {
|
||||
assert!(server_key_path.exists(), "Server key file should exist");
|
||||
|
||||
// Verify: correct permissions (key=0o600, certs=0o644)
|
||||
let key_perms = std::fs::metadata(&server_key_path).unwrap().permissions().mode() & 0o777;
|
||||
let key_perms = std::fs::metadata(&server_key_path)
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
assert_eq!(key_perms, 0o600, "Key file should have 0o600 permissions");
|
||||
|
||||
let ca_perms = std::fs::metadata(&ca_cert_path).unwrap().permissions().mode() & 0o777;
|
||||
let ca_perms = std::fs::metadata(&ca_cert_path)
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
assert_eq!(ca_perms, 0o644, "CA cert should have 0o644 permissions");
|
||||
|
||||
let server_perms = std::fs::metadata(&server_cert_path).unwrap().permissions().mode() & 0o777;
|
||||
assert_eq!(server_perms, 0o644, "Server cert should have 0o644 permissions");
|
||||
let server_perms = std::fs::metadata(&server_cert_path)
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
assert_eq!(
|
||||
server_perms, 0o644,
|
||||
"Server cert should have 0o644 permissions"
|
||||
);
|
||||
|
||||
// Verify: whitelist contains manager IP
|
||||
let wl_content = std::fs::read_to_string(&whitelist_path).unwrap();
|
||||
@ -220,14 +246,17 @@ async fn test_enrollment_denied_flow() {
|
||||
let (server, base_url) = create_mock_manager().await;
|
||||
let (cert_dir, _whitelist_dir) = create_temp_dirs();
|
||||
|
||||
let whitelist_path = _whitelist_dir.path().join("whitelist.yaml").to_string_lossy().to_string();
|
||||
let whitelist_path = _whitelist_dir
|
||||
.path()
|
||||
.join("whitelist.yaml")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
init_empty_whitelist(&whitelist_path);
|
||||
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "denied_token"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "denied_token"}"#),
|
||||
)
|
||||
.named("registration")
|
||||
.mount(&server)
|
||||
@ -235,9 +264,7 @@ async fn test_enrollment_denied_flow() {
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"/api/v1/enroll/status/.+"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_string(r#"{"status": "denied"}"#),
|
||||
)
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(r#"{"status": "denied"}"#))
|
||||
.named("status_denied")
|
||||
.expect(1) // Exactly one poll attempt before denial
|
||||
.mount(&server)
|
||||
@ -246,7 +273,10 @@ async fn test_enrollment_denied_flow() {
|
||||
let client = build_client(&base_url);
|
||||
|
||||
// Phase 1: Registration succeeds even for denied enrollment
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
assert_eq!(response.polling_token, "denied_token");
|
||||
|
||||
// Phase 2: Polling returns denial error
|
||||
@ -254,7 +284,10 @@ async fn test_enrollment_denied_flow() {
|
||||
.poll_for_approval(&response.polling_token, POLL_INTERVAL_SECONDS, 10)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err(), "Should receive error for denied enrollment");
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Should receive error for denied enrollment"
|
||||
);
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(
|
||||
err_msg.contains("denied"),
|
||||
@ -267,9 +300,18 @@ async fn test_enrollment_denied_flow() {
|
||||
let server_cert_path = cert_dir.path().join("server.pem");
|
||||
let server_key_path = cert_dir.path().join("server.key.pem");
|
||||
|
||||
assert!(!ca_path.exists(), "CA cert should NOT exist after denied enrollment");
|
||||
assert!(!server_cert_path.exists(), "Server cert should NOT exist after denied enrollment");
|
||||
assert!(!server_key_path.exists(), "Server key should NOT exist after denied enrollment");
|
||||
assert!(
|
||||
!ca_path.exists(),
|
||||
"CA cert should NOT exist after denied enrollment"
|
||||
);
|
||||
assert!(
|
||||
!server_cert_path.exists(),
|
||||
"Server cert should NOT exist after denied enrollment"
|
||||
);
|
||||
assert!(
|
||||
!server_key_path.exists(),
|
||||
"Server key should NOT exist after denied enrollment"
|
||||
);
|
||||
|
||||
// Verify: no whitelist modifications on failed enrollment
|
||||
let wl_content = std::fs::read_to_string(&whitelist_path).unwrap();
|
||||
@ -298,8 +340,7 @@ async fn test_enrollment_timeout_flow() {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "timeout_token"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "timeout_token"}"#),
|
||||
)
|
||||
.named("registration")
|
||||
.mount(&server)
|
||||
@ -307,16 +348,17 @@ async fn test_enrollment_timeout_flow() {
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"/api/v1/enroll/status/.+"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_string(r#"{"status": "pending"}"#),
|
||||
)
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(r#"{"status": "pending"}"#))
|
||||
.named("status_always_pending")
|
||||
.expect(3) // Exactly 3 poll attempts before timeout
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = build_client(&base_url);
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
|
||||
// Poll with max_attempts=3 - should timeout after exactly 3 attempts
|
||||
let result = client
|
||||
@ -337,8 +379,14 @@ async fn test_enrollment_timeout_flow() {
|
||||
let server_key_path = cert_dir.path().join("server.key.pem");
|
||||
|
||||
assert!(!ca_path.exists(), "CA cert should NOT exist after timeout");
|
||||
assert!(!server_cert_path.exists(), "Server cert should NOT exist after timeout");
|
||||
assert!(!server_key_path.exists(), "Server key should NOT exist after timeout");
|
||||
assert!(
|
||||
!server_cert_path.exists(),
|
||||
"Server cert should NOT exist after timeout"
|
||||
);
|
||||
assert!(
|
||||
!server_key_path.exists(),
|
||||
"Server key should NOT exist after timeout"
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@ -359,32 +407,32 @@ async fn test_certificate_permission_verification() {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "perm_token"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "perm_token"}"#),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"/api/v1/enroll/status/.+"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_string(&format!(
|
||||
r#"{{
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(&format!(
|
||||
r#"{{
|
||||
"status": "approved",
|
||||
"ca_crt": {},
|
||||
"server_crt": {},
|
||||
"server_key": {}
|
||||
}}"#,
|
||||
serde_json::to_string(DUMMY_CA_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_SERVER_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_KEY_PEM).unwrap(),
|
||||
)),
|
||||
)
|
||||
serde_json::to_string(DUMMY_CA_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_SERVER_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_KEY_PEM).unwrap(),
|
||||
)))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = build_client(&base_url);
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
let bundle = client
|
||||
.poll_for_approval(&response.polling_token, POLL_INTERVAL_SECONDS, 5)
|
||||
.await
|
||||
@ -397,14 +445,15 @@ async fn test_certificate_permission_verification() {
|
||||
&bundle.server_crt,
|
||||
&bundle.server_key,
|
||||
Some(&tls_config),
|
||||
).await.expect("PKI provisioning should succeed");
|
||||
)
|
||||
.await
|
||||
.expect("PKI provisioning should succeed");
|
||||
|
||||
// Verify key file: 0o600 (owner read/write only)
|
||||
let key_path = cert_dir.path().join("server.key.pem");
|
||||
let key_perms = std::fs::metadata(&key_path).unwrap().permissions().mode() & 0o777;
|
||||
assert_eq!(
|
||||
key_perms,
|
||||
0o600,
|
||||
key_perms, 0o600,
|
||||
"Key file must have exactly 0o600 permissions (owner rw only)"
|
||||
);
|
||||
|
||||
@ -412,17 +461,19 @@ async fn test_certificate_permission_verification() {
|
||||
let ca_path = cert_dir.path().join("ca.pem");
|
||||
let ca_perms = std::fs::metadata(&ca_path).unwrap().permissions().mode() & 0o777;
|
||||
assert_eq!(
|
||||
ca_perms,
|
||||
0o644,
|
||||
ca_perms, 0o644,
|
||||
"CA certificate must have exactly 0o644 permissions"
|
||||
);
|
||||
|
||||
// Verify server cert: 0o644 (owner rw, group/others read)
|
||||
let server_cert_path = cert_dir.path().join("server.pem");
|
||||
let server_perms = std::fs::metadata(&server_cert_path).unwrap().permissions().mode() & 0o777;
|
||||
let server_perms = std::fs::metadata(&server_cert_path)
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
assert_eq!(
|
||||
server_perms,
|
||||
0o644,
|
||||
server_perms, 0o644,
|
||||
"Server certificate must have exactly 0o644 permissions"
|
||||
);
|
||||
|
||||
@ -442,7 +493,9 @@ async fn test_certificate_permission_verification() {
|
||||
assert!(ca_content.contains("END CERTIFICATE"));
|
||||
|
||||
let key_content = std::fs::read_to_string(&key_path).unwrap();
|
||||
assert!(key_content.contains("BEGIN PRIVATE KEY") || key_content.contains("BEGIN RSA PRIVATE KEY"));
|
||||
assert!(
|
||||
key_content.contains("BEGIN PRIVATE KEY") || key_content.contains("BEGIN RSA PRIVATE KEY")
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@ -459,45 +512,52 @@ async fn test_whitelist_append_verification() {
|
||||
let (server, base_url) = create_mock_manager().await;
|
||||
let (_cert_dir, whitelist_dir) = create_temp_dirs();
|
||||
|
||||
let whitelist_path = whitelist_dir.path().join("whitelist.yaml").to_string_lossy().to_string();
|
||||
let whitelist_path = whitelist_dir
|
||||
.path()
|
||||
.join("whitelist.yaml")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
init_empty_whitelist(&whitelist_path);
|
||||
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "wl_token"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "wl_token"}"#),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"/api/v1/enroll/status/.+"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_string(&format!(
|
||||
r#"{{
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(&format!(
|
||||
r#"{{
|
||||
"status": "approved",
|
||||
"ca_crt": {},
|
||||
"server_crt": {},
|
||||
"server_key": {}
|
||||
}}"#,
|
||||
serde_json::to_string(DUMMY_CA_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_SERVER_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_KEY_PEM).unwrap(),
|
||||
)),
|
||||
)
|
||||
serde_json::to_string(DUMMY_CA_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_SERVER_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_KEY_PEM).unwrap(),
|
||||
)))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = build_client(&base_url);
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
let _bundle = client
|
||||
.poll_for_approval(&response.polling_token, POLL_INTERVAL_SECONDS, 5)
|
||||
.await
|
||||
.expect("Should receive approved PkiBundle");
|
||||
|
||||
// First enrollment: append to whitelist
|
||||
let manager_ip = client.manager_ip().await.expect("Should resolve manager IP");
|
||||
let manager_ip = client
|
||||
.manager_ip()
|
||||
.await
|
||||
.expect("Should resolve manager IP");
|
||||
provision::append_manager_to_whitelist(&manager_ip, &whitelist_path)
|
||||
.await
|
||||
.expect("First whitelist append should succeed");
|
||||
@ -544,7 +604,10 @@ async fn test_whitelist_append_verification() {
|
||||
);
|
||||
|
||||
// Verify: YAML format is valid and parseable
|
||||
assert!(wl_content.contains("entries:"), "YAML should contain 'entries:' key");
|
||||
assert!(
|
||||
wl_content.contains("entries:"),
|
||||
"YAML should contain 'entries:' key"
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@ -565,24 +628,24 @@ async fn test_signal_handling_during_polling() {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "signal_token"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "signal_token"}"#),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"/api/v1/enroll/status/.+"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_string(r#"{"status": "pending"}"#),
|
||||
)
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(r#"{"status": "pending"}"#))
|
||||
.named("always_pending")
|
||||
.expect(3) // Exactly 3 polls before graceful shutdown
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = build_client(&base_url);
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
|
||||
// Poll with max_attempts=3, interval=1s
|
||||
// This simulates SIGTERM interrupt by exhausting attempts (graceful shutdown)
|
||||
@ -602,8 +665,11 @@ async fn test_signal_handling_during_polling() {
|
||||
// Verify: cleanup of any partial state (no leftover files)
|
||||
for entry in std::fs::read_dir(cert_dir.path()).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
assert!(false, "No partial files should remain after graceful shutdown: {}",
|
||||
entry.file_name().to_string_lossy());
|
||||
assert!(
|
||||
false,
|
||||
"No partial files should remain after graceful shutdown: {}",
|
||||
entry.file_name().to_string_lossy()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -620,45 +686,52 @@ async fn test_whitelist_yaml_format_preservation() {
|
||||
let (server, base_url) = create_mock_manager().await;
|
||||
let (_cert_dir, whitelist_dir) = create_temp_dirs();
|
||||
|
||||
let whitelist_path = whitelist_dir.path().join("whitelist.yaml").to_string_lossy().to_string();
|
||||
let whitelist_path = whitelist_dir
|
||||
.path()
|
||||
.join("whitelist.yaml")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
init_empty_whitelist(&whitelist_path);
|
||||
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "yaml_token"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "yaml_token"}"#),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"/api/v1/enroll/status/.+"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_string(&format!(
|
||||
r#"{{
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(&format!(
|
||||
r#"{{
|
||||
"status": "approved",
|
||||
"ca_crt": {},
|
||||
"server_crt": {},
|
||||
"server_key": {}
|
||||
}}"#,
|
||||
serde_json::to_string(DUMMY_CA_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_SERVER_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_KEY_PEM).unwrap(),
|
||||
)),
|
||||
)
|
||||
serde_json::to_string(DUMMY_CA_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_SERVER_PEM).unwrap(),
|
||||
serde_json::to_string(DUMMY_KEY_PEM).unwrap(),
|
||||
)))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = build_client(&base_url);
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
let _bundle = client
|
||||
.poll_for_approval(&response.polling_token, POLL_INTERVAL_SECONDS, 5)
|
||||
.await
|
||||
.expect("Should receive approved PkiBundle");
|
||||
|
||||
// Provision and append to whitelist
|
||||
let manager_ip = client.manager_ip().await.expect("Should resolve manager IP");
|
||||
let manager_ip = client
|
||||
.manager_ip()
|
||||
.await
|
||||
.expect("Should resolve manager IP");
|
||||
provision::append_manager_to_whitelist(&manager_ip, &whitelist_path)
|
||||
.await
|
||||
.expect("Whitelist append should succeed");
|
||||
@ -667,11 +740,14 @@ async fn test_whitelist_yaml_format_preservation() {
|
||||
let wl_content = std::fs::read_to_string(&whitelist_path).unwrap();
|
||||
|
||||
// Parse as serde_yaml to verify format
|
||||
let wl_config: serde_yaml::Value = serde_yaml::from_str(&wl_content)
|
||||
.expect("Whitelist should be valid YAML after enrollment");
|
||||
let wl_config: serde_yaml::Value =
|
||||
serde_yaml::from_str(&wl_content).expect("Whitelist should be valid YAML after enrollment");
|
||||
|
||||
// Verify structure: entries key exists and is a sequence
|
||||
assert!(wl_config.get("entries").is_some(), "YAML must contain 'entries' key");
|
||||
assert!(
|
||||
wl_config.get("entries").is_some(),
|
||||
"YAML must contain 'entries' key"
|
||||
);
|
||||
let entries = wl_config.get("entries").unwrap();
|
||||
assert!(entries.is_sequence(), "'entries' must be a YAML sequence");
|
||||
|
||||
|
||||
@ -9,13 +9,11 @@
|
||||
//! - Short polling intervals ensure tests complete quickly
|
||||
//! - serial_test prevents port conflicts between concurrent test runs
|
||||
|
||||
use linux_patch_api::enroll::client::{
|
||||
EnrollmentClient,
|
||||
};
|
||||
use linux_patch_api::enroll::client::EnrollmentClient;
|
||||
use serial_test::serial;
|
||||
use wiremock::{
|
||||
Mock, MockServer, ResponseTemplate,
|
||||
matchers::{method, path, path_regex},
|
||||
Mock, MockServer, ResponseTemplate,
|
||||
};
|
||||
|
||||
/// Test constants
|
||||
@ -54,8 +52,7 @@ async fn test_successful_enrollment_flow() {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "test_token_123"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "test_token_123"}"#),
|
||||
)
|
||||
.named("enroll_registration")
|
||||
.mount(&server)
|
||||
@ -81,7 +78,10 @@ async fn test_successful_enrollment_flow() {
|
||||
let client = build_client(&base_url);
|
||||
|
||||
// Phase 1: Register - should succeed with polling token
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
assert_eq!(response.polling_token, TEST_TOKEN);
|
||||
|
||||
// Phase 2: Poll for approval - should get PkiBundle immediately since mock returns approved
|
||||
@ -89,11 +89,23 @@ async fn test_successful_enrollment_flow() {
|
||||
.poll_for_approval(TEST_TOKEN, POLL_INTERVAL_SECONDS, 5)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok(), "Polling should succeed with approved status");
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Polling should succeed with approved status"
|
||||
);
|
||||
let bundle = result.unwrap();
|
||||
assert_eq!(bundle.ca_crt, "-----BEGIN CERTIFICATE-----\nCA_CERT_DATA\n-----END CERTIFICATE-----");
|
||||
assert_eq!(bundle.server_crt, "-----BEGIN CERTIFICATE-----\nSERVER_CERT_DATA\n-----END CERTIFICATE-----");
|
||||
assert_eq!(bundle.server_key, "-----BEGIN PRIVATE KEY-----\nSERVER_KEY_DATA\n-----END PRIVATE KEY-----");
|
||||
assert_eq!(
|
||||
bundle.ca_crt,
|
||||
"-----BEGIN CERTIFICATE-----\nCA_CERT_DATA\n-----END CERTIFICATE-----"
|
||||
);
|
||||
assert_eq!(
|
||||
bundle.server_crt,
|
||||
"-----BEGIN CERTIFICATE-----\nSERVER_CERT_DATA\n-----END CERTIFICATE-----"
|
||||
);
|
||||
assert_eq!(
|
||||
bundle.server_key,
|
||||
"-----BEGIN PRIVATE KEY-----\nSERVER_KEY_DATA\n-----END PRIVATE KEY-----"
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@ -111,8 +123,7 @@ async fn test_pending_then_approved_sequence() {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "seq_token_456"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "seq_token_456"}"#),
|
||||
)
|
||||
.named("registration")
|
||||
.mount(&server)
|
||||
@ -121,16 +132,14 @@ async fn test_pending_then_approved_sequence() {
|
||||
// Status always returns approved (simplifies test while verifying the happy path)
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"/api/v1/enroll/status/.+"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_string(
|
||||
r#"{
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(
|
||||
r#"{
|
||||
"status": "approved",
|
||||
"ca_crt": "CA_PEM",
|
||||
"server_crt": "SERVER_PEM",
|
||||
"server_key": "KEY_PEM"
|
||||
}"#,
|
||||
),
|
||||
)
|
||||
))
|
||||
.named("status_approved")
|
||||
.mount(&server)
|
||||
.await;
|
||||
@ -168,8 +177,7 @@ async fn test_denied_enrollment() {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "denied_token_789"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "denied_token_789"}"#),
|
||||
)
|
||||
.named("registration")
|
||||
.mount(&server)
|
||||
@ -178,10 +186,7 @@ async fn test_denied_enrollment() {
|
||||
// Status returns denied immediately
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/api/v1/enroll/status/denied_token_789"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.set_body_string(r#"{"status": "denied"}"#),
|
||||
)
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(r#"{"status": "denied"}"#))
|
||||
.named("status_denied")
|
||||
.expect(1) // Exactly one poll attempt
|
||||
.mount(&server)
|
||||
@ -190,7 +195,10 @@ async fn test_denied_enrollment() {
|
||||
let client = build_client(&base_url);
|
||||
|
||||
// Register succeeds
|
||||
let response = client.register().await.expect("Registration should succeed even for denied enrollment");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed even for denied enrollment");
|
||||
assert_eq!(response.polling_token, "denied_token_789");
|
||||
|
||||
// Poll should return error
|
||||
@ -198,7 +206,10 @@ async fn test_denied_enrollment() {
|
||||
.poll_for_approval(&response.polling_token, POLL_INTERVAL_SECONDS, 10)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err(), "Should receive error for denied enrollment");
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Should receive error for denied enrollment"
|
||||
);
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(
|
||||
err_msg.contains("denied"),
|
||||
@ -223,8 +234,7 @@ async fn test_token_not_found_expired() {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "expired_token_000"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "expired_token_000"}"#),
|
||||
)
|
||||
.named("registration")
|
||||
.mount(&server)
|
||||
@ -233,10 +243,7 @@ async fn test_token_not_found_expired() {
|
||||
// Status returns notfound (serde rename_all="lowercase" converts NotFound -> "notfind")
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/api/v1/enroll/status/expired_token_000"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.set_body_string(r#"{"status": "notfound"}"#),
|
||||
)
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(r#"{"status": "notfound"}"#))
|
||||
.named("status_not_found")
|
||||
.expect(1) // Exactly one poll attempt
|
||||
.mount(&server)
|
||||
@ -245,7 +252,10 @@ async fn test_token_not_found_expired() {
|
||||
let client = build_client(&base_url);
|
||||
|
||||
// Register succeeds
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
|
||||
// Poll should return error about expired/invalid token
|
||||
let result = client
|
||||
@ -277,8 +287,7 @@ async fn test_max_attempts_timeout() {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "timeout_token_abc"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "timeout_token_abc"}"#),
|
||||
)
|
||||
.named("registration")
|
||||
.mount(&server)
|
||||
@ -287,10 +296,7 @@ async fn test_max_attempts_timeout() {
|
||||
// Status always returns pending - should be called exactly 3 times (max_attempts=3)
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/api/v1/enroll/status/timeout_token_abc"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.set_body_string(r#"{"status": "pending"}"#),
|
||||
)
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(r#"{"status": "pending"}"#))
|
||||
.named("status_pending_timeout")
|
||||
.expect(3) // Exactly 3 poll attempts before giving up
|
||||
.mount(&server)
|
||||
@ -298,7 +304,10 @@ async fn test_max_attempts_timeout() {
|
||||
|
||||
let client = build_client(&base_url);
|
||||
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
|
||||
// Poll with max_attempts=3, interval=1s
|
||||
let result = client
|
||||
@ -329,9 +338,10 @@ async fn test_rate_limit_on_registration() {
|
||||
// Registration returns 429
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(ResponseTemplate::new(429).set_body_string(
|
||||
r#"{"error": "Too Many Requests", "retry_after": 60}"#,
|
||||
))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(429)
|
||||
.set_body_string(r#"{"error": "Too Many Requests", "retry_after": 60}"#),
|
||||
)
|
||||
.named("registration_rate_limited")
|
||||
.expect(1) // Exactly one attempt
|
||||
.mount(&server)
|
||||
@ -382,16 +392,14 @@ async fn test_registration_payload_structure() {
|
||||
// Status endpoint (for completeness)
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"/api/v1/enroll/status/.+"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_string(
|
||||
r#"{
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(
|
||||
r#"{
|
||||
"status": "approved",
|
||||
"ca_crt": "CA_TEST",
|
||||
"server_crt": "CRT_TEST",
|
||||
"server_key": "KEY_TEST"
|
||||
}"#,
|
||||
),
|
||||
)
|
||||
))
|
||||
.named("status_approved")
|
||||
.mount(&server)
|
||||
.await;
|
||||
@ -399,34 +407,45 @@ async fn test_registration_payload_structure() {
|
||||
let client = build_client(&base_url);
|
||||
|
||||
// Execute registration and capture the actual request
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
assert_eq!(response.polling_token, "payload_test_token");
|
||||
|
||||
// Verify using server request logs
|
||||
let requests = server.received_requests().await.unwrap();
|
||||
let post_request = requests.iter()
|
||||
let post_request = requests
|
||||
.iter()
|
||||
.find(|r| r.method.to_string() == "POST")
|
||||
.expect("Should have received a POST request");
|
||||
|
||||
let body_str = std::str::from_utf8(&post_request.body).expect("Body should be valid UTF-8");
|
||||
let payload: serde_json::Value = serde_json::from_str(body_str)
|
||||
.expect("Request body should be valid JSON");
|
||||
let payload: serde_json::Value =
|
||||
serde_json::from_str(body_str).expect("Request body should be valid JSON");
|
||||
|
||||
// Verify machine_id field
|
||||
let machine_id = payload.get("machine_id")
|
||||
let machine_id = payload
|
||||
.get("machine_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.expect("machine_id field must exist and be a string");
|
||||
assert!(!machine_id.is_empty(), "machine_id should not be empty");
|
||||
assert_eq!(machine_id.len(), 32, "machine_id should be 32 characters (UUID hex)");
|
||||
assert_eq!(
|
||||
machine_id.len(),
|
||||
32,
|
||||
"machine_id should be 32 characters (UUID hex)"
|
||||
);
|
||||
|
||||
// Verify fqdn field
|
||||
let fqdn = payload.get("fqdn")
|
||||
let fqdn = payload
|
||||
.get("fqdn")
|
||||
.and_then(|v| v.as_str())
|
||||
.expect("fqdn field must exist and be a string");
|
||||
assert!(!fqdn.is_empty(), "fqdn should not be empty");
|
||||
|
||||
// Verify ip_address field
|
||||
let ip_address = payload.get("ip_address")
|
||||
let ip_address = payload
|
||||
.get("ip_address")
|
||||
.and_then(|v| v.as_str())
|
||||
.expect("ip_address field must exist and be a string");
|
||||
assert!(!ip_address.is_empty(), "ip_address should not be empty");
|
||||
@ -438,12 +457,10 @@ async fn test_registration_payload_structure() {
|
||||
);
|
||||
|
||||
// Verify os_details field is an object with expected keys
|
||||
let os_details = payload.get("os_details")
|
||||
let os_details = payload
|
||||
.get("os_details")
|
||||
.expect("os_details field must exist");
|
||||
assert!(
|
||||
os_details.is_object(),
|
||||
"os_details should be a JSON object"
|
||||
);
|
||||
assert!(os_details.is_object(), "os_details should be a JSON object");
|
||||
|
||||
let os_obj = os_details.as_object().unwrap();
|
||||
assert!(!os_obj.is_empty(), "os_details should not be empty");
|
||||
@ -469,9 +486,9 @@ async fn test_server_error_on_registration() {
|
||||
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(ResponseTemplate::new(500).set_body_string(
|
||||
r#"{"error": "Internal Server Error"}"#,
|
||||
))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(500).set_body_string(r#"{"error": "Internal Server Error"}"#),
|
||||
)
|
||||
.named("registration_server_error")
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
@ -506,8 +523,7 @@ async fn test_rate_limit_on_polling_retries() {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "rl_poll_token"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "rl_poll_token"}"#),
|
||||
)
|
||||
.named("registration")
|
||||
.mount(&server)
|
||||
@ -516,22 +532,23 @@ async fn test_rate_limit_on_polling_retries() {
|
||||
// Status returns approved on first poll
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/api/v1/enroll/status/rl_poll_token"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_string(
|
||||
r#"{
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(
|
||||
r#"{
|
||||
"status": "approved",
|
||||
"ca_crt": "CA_OK",
|
||||
"server_crt": "CRT_OK",
|
||||
"server_key": "KEY_OK"
|
||||
}"#,
|
||||
),
|
||||
)
|
||||
))
|
||||
.named("status_approved_after_retry")
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = build_client(&base_url);
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
|
||||
// Polling should succeed (mock returns approved directly)
|
||||
let bundle = client
|
||||
@ -579,8 +596,7 @@ async fn test_polling_default_parameters() {
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/v1/enroll"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(202)
|
||||
.set_body_string(r#"{"polling_token": "defaults_token"}"#),
|
||||
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "defaults_token"}"#),
|
||||
)
|
||||
.named("registration")
|
||||
.mount(&server)
|
||||
@ -589,22 +605,23 @@ async fn test_polling_default_parameters() {
|
||||
// Status returns approved immediately
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/api/v1/enroll/status/defaults_token"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_string(
|
||||
r#"{
|
||||
.respond_with(ResponseTemplate::new(200).set_body_string(
|
||||
r#"{
|
||||
"status": "approved",
|
||||
"ca_crt": "DEFAULT_CA",
|
||||
"server_crt": "DEFAULT_CRT",
|
||||
"server_key": "DEFAULT_KEY"
|
||||
}"#,
|
||||
),
|
||||
)
|
||||
))
|
||||
.named("status_approved")
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = build_client(&base_url);
|
||||
let response = client.register().await.expect("Registration should succeed");
|
||||
let response = client
|
||||
.register()
|
||||
.await
|
||||
.expect("Registration should succeed");
|
||||
|
||||
// Call with interval=0 (should default to 60) and max_attempts=0 (should default to 1440)
|
||||
// But since mock returns approved on first try, we don't actually wait
|
||||
|
||||
@ -3,7 +3,9 @@
|
||||
//! Comprehensive tests for cross-distribution identity extraction functions.
|
||||
//! Verifies machine-id, FQDN, IP address collection, and OS detail parsing.
|
||||
|
||||
use linux_patch_api::enroll::identity::{get_fqdn, get_ip_addresses, get_machine_id, get_os_details};
|
||||
use linux_patch_api::enroll::identity::{
|
||||
get_fqdn, get_ip_addresses, get_machine_id, get_os_details,
|
||||
};
|
||||
use linux_patch_api::enroll::EnrollmentRequest;
|
||||
use serde_json::Value;
|
||||
|
||||
@ -46,10 +48,7 @@ fn test_machine_id_is_consistent() {
|
||||
// Multiple calls should return the same value (it's a persistent identifier)
|
||||
let id1 = get_machine_id().expect("Failed to get machine-id (call 1)");
|
||||
let id2 = get_machine_id().expect("Failed to get machine-id (call 2)");
|
||||
assert_eq!(
|
||||
id1, id2,
|
||||
"machine-id should be consistent across calls"
|
||||
);
|
||||
assert_eq!(id1, id2, "machine-id should be consistent across calls");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -67,8 +66,12 @@ fn test_machine_id_fallback_file_check() {
|
||||
// Verify fallback file exists (may or may not be used)
|
||||
let fallback = std::path::Path::new("/var/lib/dbus/machine-id");
|
||||
if fallback.exists() {
|
||||
let content = std::fs::read_to_string(fallback).expect("Failed to read fallback machine-id");
|
||||
assert!(!content.trim().is_empty(), "Fallback machine-id should not be empty");
|
||||
let content =
|
||||
std::fs::read_to_string(fallback).expect("Failed to read fallback machine-id");
|
||||
assert!(
|
||||
!content.trim().is_empty(),
|
||||
"Fallback machine-id should not be empty"
|
||||
);
|
||||
}
|
||||
// If it doesn't exist, that's fine - primary file is used instead
|
||||
}
|
||||
@ -157,9 +160,9 @@ fn test_ip_addresses_are_valid_ipv4() {
|
||||
assert_eq!(parts.len(), 4, "IP '{}' should have 4 octets", addr);
|
||||
|
||||
for part in &parts {
|
||||
let _octet: u8 = part
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("IP octet '{}' in '{}' is not a valid number", part, addr));
|
||||
let _octet: u8 = part.parse().unwrap_or_else(|_| {
|
||||
panic!("IP octet '{}' in '{}' is not a valid number", part, addr)
|
||||
});
|
||||
// u8 parse success guarantees 0-255 range
|
||||
}
|
||||
}
|
||||
@ -198,7 +201,10 @@ fn test_ip_addresses_no_broadcast() {
|
||||
let addrs = get_ip_addresses().expect("Failed to get IP addresses");
|
||||
|
||||
for addr in &addrs {
|
||||
assert_ne!(addr, "255.255.255.255", "Broadcast address should be excluded");
|
||||
assert_ne!(
|
||||
addr, "255.255.255.255",
|
||||
"Broadcast address should be excluded"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,7 +244,11 @@ fn test_ip_addresses_are_unicast() {
|
||||
assert!(first < 240, "Address '{}' is reserved", addr);
|
||||
|
||||
// Not unspecified (0.0.0.0)
|
||||
assert!(!(parts == vec![0, 0, 0, 0]), "Address '{}' is unspecified", addr);
|
||||
assert!(
|
||||
!(parts == vec![0, 0, 0, 0]),
|
||||
"Address '{}' is unspecified",
|
||||
addr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,7 +269,9 @@ fn test_os_details_returns_valid_json_object() {
|
||||
#[test]
|
||||
fn test_os_details_contains_kernel_version() {
|
||||
let details = get_os_details().expect("Failed to get OS details");
|
||||
let kernel = details.get("kernel").expect("OS details must contain 'kernel' field");
|
||||
let kernel = details
|
||||
.get("kernel")
|
||||
.expect("OS details must contain 'kernel' field");
|
||||
assert!(kernel.is_string(), "Kernel version should be a string");
|
||||
|
||||
let kernel_str = kernel.as_str().unwrap();
|
||||
@ -297,7 +309,10 @@ fn test_os_details_distro_is_valid_string() {
|
||||
assert!(distro.is_string(), "Distro should be a string");
|
||||
let distro_str = distro.as_str().unwrap();
|
||||
assert!(!distro_str.is_empty(), "Distro name should not be empty");
|
||||
assert_ne!(distro_str, "unknown", "Distro should be identified on this system");
|
||||
assert_ne!(
|
||||
distro_str, "unknown",
|
||||
"Distro should be identified on this system"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -350,7 +365,8 @@ fn test_enrollment_payload_construction() {
|
||||
let os_details = get_os_details().expect("Failed to get OS details");
|
||||
|
||||
// Use first non-loopback IP as the primary address
|
||||
let primary_ip = ip_addrs.first()
|
||||
let primary_ip = ip_addrs
|
||||
.first()
|
||||
.expect("Should have at least one IP")
|
||||
.clone();
|
||||
|
||||
@ -362,19 +378,30 @@ fn test_enrollment_payload_construction() {
|
||||
};
|
||||
|
||||
// Verify payload serializes to valid JSON
|
||||
let json = serde_json::to_string(&request)
|
||||
.expect("EnrollmentRequest should serialize to valid JSON");
|
||||
let json =
|
||||
serde_json::to_string(&request).expect("EnrollmentRequest should serialize to valid JSON");
|
||||
|
||||
assert!(!json.is_empty(), "Serialized enrollment request should not be empty");
|
||||
assert!(
|
||||
!json.is_empty(),
|
||||
"Serialized enrollment request should not be empty"
|
||||
);
|
||||
|
||||
// Verify JSON contains all required fields
|
||||
let parsed: Value = serde_json::from_str(&json)
|
||||
.expect("Should deserialize enrollment request");
|
||||
let parsed: Value = serde_json::from_str(&json).expect("Should deserialize enrollment request");
|
||||
|
||||
assert!(parsed.get("machine_id").is_some(), "JSON must contain machine_id");
|
||||
assert!(
|
||||
parsed.get("machine_id").is_some(),
|
||||
"JSON must contain machine_id"
|
||||
);
|
||||
assert!(parsed.get("fqdn").is_some(), "JSON must contain fqdn");
|
||||
assert!(parsed.get("ip_address").is_some(), "JSON must contain ip_address");
|
||||
assert!(parsed.get("os_details").is_some(), "JSON must contain os_details");
|
||||
assert!(
|
||||
parsed.get("ip_address").is_some(),
|
||||
"JSON must contain ip_address"
|
||||
);
|
||||
assert!(
|
||||
parsed.get("os_details").is_some(),
|
||||
"JSON must contain os_details"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -430,8 +457,8 @@ fn test_enrollment_payload_roundtrip() {
|
||||
|
||||
// Serialize to JSON then deserialize back
|
||||
let json = serde_json::to_string(&request).expect("Failed to serialize");
|
||||
let deserialized: EnrollmentRequest = serde_json::from_str(&json)
|
||||
.expect("Failed to deserialize enrollment request");
|
||||
let deserialized: EnrollmentRequest =
|
||||
serde_json::from_str(&json).expect("Failed to deserialize enrollment request");
|
||||
|
||||
assert_eq!(request.machine_id, deserialized.machine_id);
|
||||
assert_eq!(request.fqdn, deserialized.fqdn);
|
||||
@ -461,7 +488,10 @@ fn test_cross_distro_os_release_parsing() {
|
||||
}
|
||||
|
||||
// Verify key fields are present (POSIX standard for os-release)
|
||||
assert!(parsed.contains_key("NAME"), "os-release must contain NAME field");
|
||||
assert!(
|
||||
parsed.contains_key("NAME"),
|
||||
"os-release must contain NAME field"
|
||||
);
|
||||
assert!(parsed["NAME"].ne(&""), "NAME should not be empty");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user