#[cfg(feature = "acme")]
mod acme_tests {
use potato::acme::{AcmeOptions, DynamicTlsAcceptor};
use std::time::Duration;
use std::time::SystemTime;
#[test]
fn test_acme_options_creation() {
let opts = AcmeOptions::new("example.com", "test@example.com");
assert_eq!(opts.domains, vec!["example.com"]);
assert_eq!(opts.email, "test@example.com");
assert!(opts.acme_directory.is_none());
assert!(opts.cert_dir.is_none());
}
#[test]
fn test_acme_options_custom() {
let opts = AcmeOptions {
domains: vec!["example.com".to_string(), "www.example.com".to_string()],
email: "test@example.com".to_string(),
acme_directory: Some(
"https://acme-staging-v02.api.letsencrypt.org/directory".to_string(),
),
cert_dir: Some("/tmp/test_certs".to_string()),
};
assert_eq!(opts.domains.len(), 2);
assert_eq!(
opts.acme_directory.unwrap(),
"https://acme-staging-v02.api.letsencrypt.org/directory"
);
assert_eq!(opts.cert_dir.unwrap(), "/tmp/test_certs");
}
#[tokio::test]
async fn test_dynamic_tls_acceptor_creation() {
let (cert_pem, key_pem) = generate_real_test_cert();
let acceptor = DynamicTlsAcceptor::new(&cert_pem, &key_pem);
assert!(acceptor.is_ok());
let acceptor = acceptor.unwrap();
let tls_acceptor = acceptor.get_acceptor().await;
drop(tls_acceptor);
}
#[tokio::test]
async fn test_dynamic_tls_acceptor_reload() {
let (cert_pem, key_pem) = generate_real_test_cert();
let acceptor = DynamicTlsAcceptor::new(&cert_pem, &key_pem).unwrap();
let initial_acceptor = acceptor.get_acceptor().await;
let initial_ptr = &initial_acceptor as *const _;
drop(initial_acceptor);
let (new_cert_pem, new_key_pem) = generate_real_test_cert();
let reload_result = acceptor.reload(&new_cert_pem, &new_key_pem).await;
assert!(reload_result.is_ok());
let reloaded_acceptor = acceptor.get_acceptor().await;
let reloaded_ptr = &reloaded_acceptor as *const _;
drop(reloaded_acceptor);
assert!(!std::ptr::eq(initial_ptr, reloaded_ptr));
}
#[test]
fn test_should_renew_logic() {
let temp_dir = std::env::temp_dir().join("potato_acme_test");
std::fs::create_dir_all(&temp_dir).unwrap();
let cert_path = temp_dir.join("cert.pem");
let (cert_pem, _) = generate_real_test_cert();
std::fs::write(&cert_path, &cert_pem).unwrap();
let metadata = std::fs::metadata(&cert_path).unwrap();
let modified = metadata.modified().unwrap();
let elapsed = modified.elapsed().unwrap();
assert!(elapsed < Duration::from_secs(60));
let _ = std::fs::remove_file(&cert_path);
let _ = std::fs::remove_dir(&temp_dir);
}
#[test]
fn test_acme_challenge_structure() {
use potato::acme::AcmeChallenge;
let challenge = AcmeChallenge {
token: "test_token".to_string(),
key_authorization: "test_key_auth".to_string(),
};
assert_eq!(challenge.token, "test_token");
assert_eq!(challenge.key_authorization, "test_key_auth");
}
#[test]
fn test_certificate_expiry_parsing() {
use rustls_pki_types::pem::PemObject;
use rustls_pki_types::CertificateDer;
let (cert_pem, _) = generate_real_test_cert();
if let Ok(certs) =
CertificateDer::pem_slice_iter(cert_pem.as_bytes()).collect::<Result<Vec<_>, _>>()
{
if let Some(cert) = certs.first() {
assert!(!cert.is_empty());
}
}
}
#[test]
fn test_should_renew_with_old_cert() {
use std::fs;
use std::time::Duration;
let temp_dir = std::env::temp_dir().join("potato_acme_renew_test");
std::fs::create_dir_all(&temp_dir).unwrap();
let cert_path = temp_dir.join("cert.pem");
let (cert_pem, _) = generate_real_test_cert();
fs::write(&cert_path, &cert_pem).unwrap();
let _sixty_one_days_ago = SystemTime::now() - Duration::from_secs(61 * 24 * 3600);
assert!(cert_path.exists());
let _ = fs::remove_file(&cert_path);
let _ = fs::remove_dir(&temp_dir);
}
fn generate_real_test_cert() -> (String, String) {
use rcgen::{CertificateParams, DistinguishedName, KeyPair};
let mut params = CertificateParams::new(vec!["test.example.com".to_string()]).unwrap();
params.distinguished_name = DistinguishedName::new();
let key_pair = KeyPair::generate().unwrap();
let cert = params.self_signed(&key_pair).unwrap();
let cert_pem = cert.pem();
let key_pem = key_pair.serialize_pem();
(cert_pem, key_pem)
}
}
#[cfg(feature = "acme")]
#[cfg(test)]
mod acme_integration_tests {
use potato::acme::AcmeOptions;
use potato::HttpServer;
use std::time::Duration;
#[tokio::test]
#[ignore] async fn test_acme_server_init() {
let _opts = potato::acme::AcmeOptions {
domains: vec!["test.example.com".to_string()], email: "test@example.com".to_string(),
acme_directory: Some(
"https://acme-staging-v02.api.letsencrypt.org/directory".to_string(),
),
cert_dir: Some("/tmp/potato_acme_staging_test".to_string()),
};
let _server = HttpServer::new("127.0.0.1:8443");
println!("ACME staging test - requires real domain configuration");
}
#[tokio::test]
async fn test_acme_challenge_response_format() {
use potato::acme::AcmeChallenge;
let challenge = AcmeChallenge {
token: "abc123".to_string(),
key_authorization: "abc123.xyz789".to_string(),
};
let expected_response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
challenge.key_authorization.len(),
challenge.key_authorization
);
assert!(expected_response.contains("HTTP/1.1 200 OK"));
assert!(expected_response.contains("Content-Type: text/plain"));
assert!(expected_response.contains("abc123.xyz789"));
}
#[test]
fn test_cert_directory_creation() {
let temp_dir = std::env::temp_dir().join("potato_acme_cert_dir_test");
let _ = std::fs::remove_dir_all(&temp_dir);
assert!(!temp_dir.exists());
std::fs::create_dir_all(&temp_dir).unwrap();
assert!(temp_dir.exists());
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_account_persistence_structure() {
let temp_dir = std::env::temp_dir().join("potato_acme_account_test");
std::fs::create_dir_all(&temp_dir).unwrap();
let account_path = temp_dir.join("account.json");
let account_data = serde_json::json!({
"kid": "https://acme-v02.api.letsencrypt.org/acme/acct/12345",
"key": {
"kty": "RSA",
"n": "test_modulus",
"e": "AQAB"
}
});
let account_str = serde_json::to_string_pretty(&account_data).unwrap();
std::fs::write(&account_path, &account_str).unwrap();
let loaded = std::fs::read_to_string(&account_path).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&loaded).unwrap();
assert_eq!(
parsed["kid"],
"https://acme-v02.api.letsencrypt.org/acme/acct/12345"
);
let _ = std::fs::remove_file(&account_path);
let _ = std::fs::remove_dir(&temp_dir);
}
#[test]
fn test_renewal_interval_logic() {
let renewal_threshold = Duration::from_secs(60 * 24 * 3600);
assert_eq!(renewal_threshold.as_secs(), 5_184_000);
let check_interval = Duration::from_secs(6 * 3600);
assert_eq!(check_interval.as_secs(), 21_600);
}
#[tokio::test]
async fn test_certificate_renewal_flow_simulation() {
use potato::acme::DynamicTlsAcceptor;
use std::fs;
let temp_dir = std::env::temp_dir().join("potato_acme_renewal_flow_test");
std::fs::create_dir_all(&temp_dir).unwrap();
let (cert_pem1, key_pem1) = generate_test_cert_for_renewal_test();
let cert_path = temp_dir.join("cert.pem");
let key_path = temp_dir.join("key.pem");
fs::write(&cert_path, &cert_pem1).unwrap();
fs::write(&key_path, &key_pem1).unwrap();
let acceptor = DynamicTlsAcceptor::new(&cert_pem1, &key_pem1).unwrap();
let initial_acceptor = acceptor.get_acceptor().await;
drop(initial_acceptor);
let (cert_pem2, key_pem2) = generate_test_cert_for_renewal_test();
fs::write(&cert_path, &cert_pem2).unwrap();
fs::write(&key_path, &key_pem2).unwrap();
let reload_result = acceptor.reload(&cert_pem2, &key_pem2).await;
assert!(reload_result.is_ok());
let reloaded_acceptor = acceptor.get_acceptor().await;
drop(reloaded_acceptor);
let _ = fs::remove_dir_all(&temp_dir);
}
fn generate_test_cert_for_renewal_test() -> (String, String) {
use rcgen::{CertificateParams, DistinguishedName, KeyPair};
let mut params =
CertificateParams::new(vec!["renewal.test.example.com".to_string()]).unwrap();
params.distinguished_name = DistinguishedName::new();
let key_pair = KeyPair::generate().unwrap();
let cert = params.self_signed(&key_pair).unwrap();
let cert_pem = cert.pem();
let key_pem = key_pair.serialize_pem();
(cert_pem, key_pem)
}
#[tokio::test]
async fn test_challenge_update_after_renewal() {
use potato::acme::AcmeChallenge;
let old_challenges = vec![
AcmeChallenge {
token: "old_token_1".to_string(),
key_authorization: "old_key_auth_1".to_string(),
},
AcmeChallenge {
token: "old_token_2".to_string(),
key_authorization: "old_key_auth_2".to_string(),
},
];
let new_challenges = vec![AcmeChallenge {
token: "new_token_1".to_string(),
key_authorization: "new_key_auth_1".to_string(),
}];
assert_ne!(old_challenges.len(), new_challenges.len());
assert_ne!(old_challenges[0].token, new_challenges[0].token);
println!("✓ 挑战更新机制验证通过");
}
#[test]
fn test_multi_domain_renewal_scenario() {
let temp_dir = std::env::temp_dir().join("potato_acme_multi_domain_test");
std::fs::create_dir_all(&temp_dir).unwrap();
let opts = AcmeOptions {
domains: vec![
"example.com".to_string(),
"www.example.com".to_string(),
"api.example.com".to_string(),
],
email: "admin@example.com".to_string(),
acme_directory: Some(
"https://acme-staging-v02.api.letsencrypt.org/directory".to_string(),
),
cert_dir: Some(temp_dir.to_str().unwrap().to_string()),
};
assert_eq!(opts.domains.len(), 3);
assert!(opts.domains.contains(&"example.com".to_string()));
assert!(opts.domains.contains(&"www.example.com".to_string()));
assert!(opts.domains.contains(&"api.example.com".to_string()));
let _ = std::fs::remove_dir_all(&temp_dir);
println!("✓ 多域名续期场景配置正确");
}
#[test]
fn test_renewal_loop_configuration() {
let check_interval = Duration::from_secs(6 * 3600); let renewal_threshold_days = 60; let certificate_validity_days = 90;
assert_eq!(check_interval.as_secs(), 21_600);
assert!(renewal_threshold_days < certificate_validity_days);
assert_eq!(
certificate_validity_days - renewal_threshold_days,
30,
"应该提前30天续期"
);
println!("✓ 续期循环配置参数正确");
}
}