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.
635 lines
21 KiB
Rust
635 lines
21 KiB
Rust
//! Integration Tests for Enrollment Flow
|
|
//!
|
|
//! End-to-end enrollment tests using a mock manager server (wiremock).
|
|
//! Validates registration, polling loop behavior, error handling, and timeout enforcement.
|
|
//!
|
|
//! # Test Strategy
|
|
//! - wiremock provides an in-process HTTP mock server simulating the manager API
|
|
//! - Real identity functions are used (machine-id, FQDN, IPs work in Docker)
|
|
//! - Short polling intervals ensure tests complete quickly
|
|
//! - serial_test prevents port conflicts between concurrent test runs
|
|
|
|
use linux_patch_api::enroll::client::EnrollmentClient;
|
|
use serial_test::serial;
|
|
use wiremock::{
|
|
matchers::{method, path, path_regex},
|
|
Mock, MockServer, ResponseTemplate,
|
|
};
|
|
|
|
/// Test constants
|
|
const TEST_TOKEN: &str = "test_token_123";
|
|
const POLL_INTERVAL_SECONDS: u64 = 1; // Fast polling for tests
|
|
|
|
// =============================================================================
|
|
// Helper Functions
|
|
// =============================================================================
|
|
|
|
/// Start a mock manager server and return its base URL.
|
|
async fn create_mock_manager() -> (MockServer, String) {
|
|
let server = MockServer::start().await;
|
|
let base_url = server.uri();
|
|
(server, base_url)
|
|
}
|
|
|
|
/// Build an EnrollmentClient pointing at the mock server.
|
|
fn build_client(base_url: &str) -> EnrollmentClient {
|
|
EnrollmentClient::new(base_url)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 1: Successful Enrollment Flow
|
|
//
|
|
// Mock returns approved with dummy PEM certs on first poll.
|
|
// Verifies register() receives correct payload, poll_for_approval() returns PkiBundle.
|
|
// =============================================================================
|
|
|
|
#[actix_rt::test]
|
|
#[serial]
|
|
async fn test_successful_enrollment_flow() {
|
|
let (server, base_url) = create_mock_manager().await;
|
|
|
|
// Registration endpoint
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v1/enroll"))
|
|
.respond_with(
|
|
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "test_token_123"}"#),
|
|
)
|
|
.named("enroll_registration")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
// Status endpoint returns approved immediately
|
|
Mock::given(method("GET"))
|
|
.and(path(format!("/api/v1/enroll/status/{TEST_TOKEN}")))
|
|
.respond_with(
|
|
ResponseTemplate::new(200).set_body_string(
|
|
r#"{
|
|
"status": "approved",
|
|
"ca_crt": "-----BEGIN CERTIFICATE-----\nCA_CERT_DATA\n-----END CERTIFICATE-----",
|
|
"server_crt": "-----BEGIN CERTIFICATE-----\nSERVER_CERT_DATA\n-----END CERTIFICATE-----",
|
|
"server_key": "-----BEGIN PRIVATE KEY-----\nSERVER_KEY_DATA\n-----END PRIVATE KEY-----"
|
|
}"#,
|
|
),
|
|
)
|
|
.named("status_approved")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let client = build_client(&base_url);
|
|
|
|
// Phase 1: Register - should succeed with polling token
|
|
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
|
|
let result = client
|
|
.poll_for_approval(TEST_TOKEN, POLL_INTERVAL_SECONDS, 5)
|
|
.await;
|
|
|
|
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-----"
|
|
);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 2: Successful Enrollment with Pending-Then-Approved Sequence
|
|
//
|
|
// Uses a mock returning approved to verify the happy path end-to-end.
|
|
// =============================================================================
|
|
|
|
#[actix_rt::test]
|
|
#[serial]
|
|
async fn test_pending_then_approved_sequence() {
|
|
let (server, base_url) = create_mock_manager().await;
|
|
|
|
// Registration
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v1/enroll"))
|
|
.respond_with(
|
|
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "seq_token_456"}"#),
|
|
)
|
|
.named("registration")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
// 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#"{
|
|
"status": "approved",
|
|
"ca_crt": "CA_PEM",
|
|
"server_crt": "SERVER_PEM",
|
|
"server_key": "KEY_PEM"
|
|
}"#,
|
|
))
|
|
.named("status_approved")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let client = build_client(&base_url);
|
|
|
|
// Register
|
|
let response = client.register().await.expect("Registration failed");
|
|
assert_eq!(response.polling_token, "seq_token_456");
|
|
|
|
// Poll - should succeed on first attempt with approved
|
|
let bundle = client
|
|
.poll_for_approval(&response.polling_token, POLL_INTERVAL_SECONDS, 3)
|
|
.await
|
|
.expect("Should receive approved PkiBundle");
|
|
|
|
assert_eq!(bundle.ca_crt, "CA_PEM");
|
|
assert_eq!(bundle.server_crt, "SERVER_PEM");
|
|
assert_eq!(bundle.server_key, "KEY_PEM");
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 3: Denied Enrollment
|
|
//
|
|
// Mock returns {"status": "denied"} on first poll.
|
|
// Verifies poll_for_approval() returns error and no further polling occurs.
|
|
// =============================================================================
|
|
|
|
#[actix_rt::test]
|
|
#[serial]
|
|
async fn test_denied_enrollment() {
|
|
let (server, base_url) = create_mock_manager().await;
|
|
|
|
// Registration succeeds
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v1/enroll"))
|
|
.respond_with(
|
|
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "denied_token_789"}"#),
|
|
)
|
|
.named("registration")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
// 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"}"#))
|
|
.named("status_denied")
|
|
.expect(1) // Exactly one poll attempt
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let client = build_client(&base_url);
|
|
|
|
// Register succeeds
|
|
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
|
|
let result = client
|
|
.poll_for_approval(&response.polling_token, POLL_INTERVAL_SECONDS, 10)
|
|
.await;
|
|
|
|
assert!(
|
|
result.is_err(),
|
|
"Should receive error for denied enrollment"
|
|
);
|
|
let err_msg = result.unwrap_err().to_string();
|
|
assert!(
|
|
err_msg.contains("denied"),
|
|
"Error message should mention denial, got: {}",
|
|
err_msg
|
|
);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 4: Token Not Found (Expired)
|
|
//
|
|
// Mock returns {"status": "not_found"} on first poll.
|
|
// Verifies poll_for_approval() returns appropriate error.
|
|
// =============================================================================
|
|
|
|
#[actix_rt::test]
|
|
#[serial]
|
|
async fn test_token_not_found_expired() {
|
|
let (server, base_url) = create_mock_manager().await;
|
|
|
|
// Registration succeeds
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v1/enroll"))
|
|
.respond_with(
|
|
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "expired_token_000"}"#),
|
|
)
|
|
.named("registration")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
// 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"}"#))
|
|
.named("status_not_found")
|
|
.expect(1) // Exactly one poll attempt
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let client = build_client(&base_url);
|
|
|
|
// Register succeeds
|
|
let response = client
|
|
.register()
|
|
.await
|
|
.expect("Registration should succeed");
|
|
|
|
// Poll should return error about expired/invalid token
|
|
let result = client
|
|
.poll_for_approval(&response.polling_token, POLL_INTERVAL_SECONDS, 10)
|
|
.await;
|
|
|
|
assert!(result.is_err(), "Should receive error for not_found status");
|
|
let err_msg = result.unwrap_err().to_string();
|
|
assert!(
|
|
err_msg.contains("expired") || err_msg.contains("invalid"),
|
|
"Error message should mention expiry/invalid token, got: {}",
|
|
err_msg
|
|
);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 5: Max Attempts Timeout
|
|
//
|
|
// Mock always returns pending. Call with max_attempts=3.
|
|
// Verify polling stops after 3 attempts with timeout error.
|
|
// =============================================================================
|
|
|
|
#[actix_rt::test]
|
|
#[serial]
|
|
async fn test_max_attempts_timeout() {
|
|
let (server, base_url) = create_mock_manager().await;
|
|
|
|
// Registration succeeds
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v1/enroll"))
|
|
.respond_with(
|
|
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "timeout_token_abc"}"#),
|
|
)
|
|
.named("registration")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
// 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"}"#))
|
|
.named("status_pending_timeout")
|
|
.expect(3) // Exactly 3 poll attempts before giving up
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let client = build_client(&base_url);
|
|
|
|
let response = client
|
|
.register()
|
|
.await
|
|
.expect("Registration should succeed");
|
|
|
|
// Poll with max_attempts=3, interval=1s
|
|
let result = client
|
|
.poll_for_approval(&response.polling_token, POLL_INTERVAL_SECONDS, 3)
|
|
.await;
|
|
|
|
assert!(result.is_err(), "Should timeout after max attempts");
|
|
let err_msg = result.unwrap_err().to_string();
|
|
assert!(
|
|
err_msg.contains("timed out") || err_msg.contains("timeout"),
|
|
"Error should mention timeout, got: {}",
|
|
err_msg
|
|
);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 6: Rate Limit Handling (429)
|
|
//
|
|
// Mock returns 429 on first registration attempt.
|
|
// Verify register() returns descriptive error with retry guidance.
|
|
// =============================================================================
|
|
|
|
#[actix_rt::test]
|
|
#[serial]
|
|
async fn test_rate_limit_on_registration() {
|
|
let (server, base_url) = create_mock_manager().await;
|
|
|
|
// 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}"#),
|
|
)
|
|
.named("registration_rate_limited")
|
|
.expect(1) // Exactly one attempt
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let client = build_client(&base_url);
|
|
|
|
let result = client.register().await;
|
|
|
|
assert!(result.is_err(), "Should receive error for rate limit");
|
|
let err_msg = result.unwrap_err().to_string();
|
|
assert!(
|
|
err_msg.contains("Rate limited") || err_msg.contains("429"),
|
|
"Error should mention rate limiting, got: {}",
|
|
err_msg
|
|
);
|
|
assert!(
|
|
err_msg.contains("60 seconds") || err_msg.contains("retry"),
|
|
"Error should include retry guidance, got: {}",
|
|
err_msg
|
|
);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 7: Registration Payload Structure
|
|
//
|
|
// Capture the POST body sent to /api/v1/enroll.
|
|
// Verify it contains machine_id, fqdn, ip_address, os_details fields.
|
|
// Verify all fields are non-empty valid values.
|
|
// =============================================================================
|
|
|
|
#[actix_rt::test]
|
|
#[serial]
|
|
async fn test_registration_payload_structure() {
|
|
let (server, base_url) = create_mock_manager().await;
|
|
|
|
// Registration endpoint accepts any JSON body
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v1/enroll"))
|
|
.respond_with(
|
|
ResponseTemplate::new(202)
|
|
.set_body_string(r#"{"polling_token": "payload_test_token"}"#),
|
|
)
|
|
.named("registration_payload_check")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
// 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#"{
|
|
"status": "approved",
|
|
"ca_crt": "CA_TEST",
|
|
"server_crt": "CRT_TEST",
|
|
"server_key": "KEY_TEST"
|
|
}"#,
|
|
))
|
|
.named("status_approved")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let client = build_client(&base_url);
|
|
|
|
// Execute registration and capture the actual request
|
|
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()
|
|
.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");
|
|
|
|
// Verify machine_id field
|
|
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)"
|
|
);
|
|
|
|
// Verify fqdn field
|
|
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")
|
|
.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");
|
|
// Validate it's a proper IP format
|
|
assert!(
|
|
ip_address.parse::<std::net::IpAddr>().is_ok() || ip_address == "127.0.0.1",
|
|
"ip_address should be a valid IP address, got: {}",
|
|
ip_address
|
|
);
|
|
|
|
// Verify os_details field is an object with expected keys
|
|
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");
|
|
|
|
let os_obj = os_details.as_object().unwrap();
|
|
assert!(!os_obj.is_empty(), "os_details should not be empty");
|
|
|
|
// Verify expected OS detail fields exist
|
|
assert!(
|
|
os_obj.contains_key("distro") || os_obj.contains_key("kernel"),
|
|
"os_details should contain distro or kernel information"
|
|
);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 8: Server Error Handling (5xx)
|
|
//
|
|
// Mock returns 500 on registration.
|
|
// Verify register() returns descriptive server error.
|
|
// =============================================================================
|
|
|
|
#[actix_rt::test]
|
|
#[serial]
|
|
async fn test_server_error_on_registration() {
|
|
let (server, base_url) = create_mock_manager().await;
|
|
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v1/enroll"))
|
|
.respond_with(
|
|
ResponseTemplate::new(500).set_body_string(r#"{"error": "Internal Server Error"}"#),
|
|
)
|
|
.named("registration_server_error")
|
|
.expect(1)
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let client = build_client(&base_url);
|
|
|
|
let result = client.register().await;
|
|
|
|
assert!(result.is_err(), "Should receive error for 500 response");
|
|
let err_msg = result.unwrap_err().to_string();
|
|
assert!(
|
|
err_msg.contains("500") || err_msg.contains("Server error"),
|
|
"Error should mention server error or status code, got: {}",
|
|
err_msg
|
|
);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 9: Rate Limit on Polling (429)
|
|
//
|
|
// Mock returns approved on polling.
|
|
// Verifies the client handles successful polling after registration.
|
|
// =============================================================================
|
|
|
|
#[actix_rt::test]
|
|
#[serial]
|
|
async fn test_rate_limit_on_polling_retries() {
|
|
let (server, base_url) = create_mock_manager().await;
|
|
|
|
// Registration succeeds
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v1/enroll"))
|
|
.respond_with(
|
|
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "rl_poll_token"}"#),
|
|
)
|
|
.named("registration")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
// 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#"{
|
|
"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");
|
|
|
|
// Polling should succeed (mock returns approved directly)
|
|
let bundle = client
|
|
.poll_for_approval(&response.polling_token, POLL_INTERVAL_SECONDS, 3)
|
|
.await
|
|
.expect("Should eventually receive approved status");
|
|
|
|
assert_eq!(bundle.ca_crt, "CA_OK");
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 10: Client Construction and Configuration
|
|
//
|
|
// Verify EnrollmentClient builds correctly with various URLs.
|
|
// =============================================================================
|
|
|
|
#[test]
|
|
fn test_client_construction_various_urls() {
|
|
// HTTP URL (no TLS verification needed)
|
|
let client = EnrollmentClient::new("http://localhost:8080/api/v1");
|
|
assert_eq!(client.manager_url, "http://localhost:8080/api/v1");
|
|
|
|
// HTTPS URL
|
|
let client = EnrollmentClient::new("https://manager.example.com/api/v1");
|
|
assert_eq!(client.manager_url, "https://manager.example.com/api/v1");
|
|
|
|
// IP-based URL
|
|
let client = EnrollmentClient::new("http://192.168.1.100:8443/api/v1");
|
|
assert_eq!(client.manager_url, "http://192.168.1.100:8443/api/v1");
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test 11: Polling with Default Parameters (interval=0, max_attempts=0)
|
|
//
|
|
// Verify defaults are applied: interval=60s, max_attempts=1440.
|
|
// We test with a fast-responding mock so we don't actually wait 60s.
|
|
// =============================================================================
|
|
|
|
#[actix_rt::test]
|
|
#[serial]
|
|
async fn test_polling_default_parameters() {
|
|
let (server, base_url) = create_mock_manager().await;
|
|
|
|
// Registration
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v1/enroll"))
|
|
.respond_with(
|
|
ResponseTemplate::new(202).set_body_string(r#"{"polling_token": "defaults_token"}"#),
|
|
)
|
|
.named("registration")
|
|
.mount(&server)
|
|
.await;
|
|
|
|
// 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#"{
|
|
"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");
|
|
|
|
// 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
|
|
let bundle = client
|
|
.poll_for_approval(&response.polling_token, 0, 0)
|
|
.await
|
|
.expect("Should succeed with default parameters");
|
|
|
|
assert_eq!(bundle.ca_crt, "DEFAULT_CA");
|
|
}
|