mod common;
use common::get_test_lock;
use std::env;
use std::fs;
use std::io::Write;
use tempfile::TempDir;
#[test]
fn test_csr_file_not_found() {
let _lock = get_test_lock();
let temp_dir = TempDir::new().unwrap();
unsafe {
env::set_var("CAROOT", temp_dir.path().to_str().unwrap());
}
let non_existent = temp_dir.path().join("nonexistent.csr");
let result = fastcert::cert::read_csr_file(non_existent.to_str().unwrap());
assert!(result.is_err(), "Should fail when CSR file doesn't exist");
unsafe {
env::remove_var("CAROOT");
}
}
#[test]
fn test_csr_invalid_pem_format() {
let _lock = get_test_lock();
let temp_dir = TempDir::new().unwrap();
unsafe {
env::set_var("CAROOT", temp_dir.path().to_str().unwrap());
}
let invalid_csr = temp_dir.path().join("invalid.csr");
let mut file = fs::File::create(&invalid_csr).unwrap();
file.write_all(b"This is not a valid PEM file").unwrap();
drop(file);
let csr_bytes = fs::read(&invalid_csr).unwrap();
let result = fastcert::cert::parse_csr_pem(&csr_bytes);
assert!(result.is_err(), "Should fail with invalid PEM format");
unsafe {
env::remove_var("CAROOT");
}
}
#[test]
fn test_csr_no_pem_markers() {
let _lock = get_test_lock();
let content = b"Some random content without PEM markers";
let result = fastcert::cert::parse_csr_pem(content);
assert!(result.is_err(), "Should fail when no PEM markers found");
}
#[test]
fn test_csr_invalid_utf8() {
let _lock = get_test_lock();
let invalid_utf8: Vec<u8> = vec![0xFF, 0xFE, 0xFD];
let result = fastcert::cert::parse_csr_pem(&invalid_utf8);
assert!(result.is_err(), "Should fail with invalid UTF-8");
}
#[test]
fn test_domain_to_ascii_valid() {
let _lock = get_test_lock();
let result = fastcert::cert::domain_to_ascii("example.com");
assert!(result.is_ok(), "Should handle valid ASCII domain");
assert_eq!(result.unwrap(), "example.com");
}
#[test]
fn test_validate_uri_empty_scheme() {
let _lock = get_test_lock();
let result = fastcert::cert::validate_uri("://example.com");
assert!(result.is_ok() || result.is_err()); }
#[test]
fn test_validate_uri_whitespace() {
let _lock = get_test_lock();
let result = fastcert::cert::validate_uri("https://example .com");
assert!(result.is_err(), "Should fail with whitespace in URI");
}
#[test]
fn test_validate_uri_no_host() {
let _lock = get_test_lock();
let result = fastcert::cert::validate_uri("https://");
assert!(result.is_err(), "Should fail with no host");
}
#[test]
fn test_validate_hostname_invalid_chars() {
let _lock = get_test_lock();
let result = fastcert::cert::validate_hostname("invalid@hostname");
assert!(result.is_err(), "Should fail with invalid characters");
}
#[test]
fn test_validate_hostname_empty() {
let _lock = get_test_lock();
let result = fastcert::cert::validate_hostname("");
assert!(result.is_err(), "Should fail with empty hostname");
}
#[test]
fn test_wildcard_depth_too_deep() {
let _lock = get_test_lock();
let result = fastcert::cert::validate_wildcard_depth("*.*.example.com");
assert!(result.is_err(), "Should fail with double wildcard");
}
#[test]
fn test_wildcard_depth_single() {
let _lock = get_test_lock();
let result = fastcert::cert::validate_wildcard_depth("*.example.com");
assert!(result.is_ok(), "Single wildcard should be valid");
}
#[test]
fn test_empty_hosts_list() {
let _lock = get_test_lock();
let temp_dir = TempDir::new().unwrap();
unsafe {
env::set_var("CAROOT", temp_dir.path().to_str().unwrap());
}
let empty_hosts: Vec<String> = vec![];
let result =
fastcert::cert::generate_certificate(&empty_hosts, None, None, None, false, false, false);
assert!(result.is_err(), "Should fail with empty hosts list");
unsafe {
env::remove_var("CAROOT");
}
}
#[test]
fn test_invalid_email_format() {
let _lock = get_test_lock();
let result = fastcert::cert::validate_email_address("invalid-email");
assert!(result.is_err(), "Should fail with invalid email");
}
#[test]
fn test_invalid_email_no_at() {
let _lock = get_test_lock();
let result = fastcert::cert::validate_email_address("nodomain.com");
assert!(result.is_err(), "Should fail with email missing @");
}
#[test]
fn test_invalid_email_empty() {
let _lock = get_test_lock();
let result = fastcert::cert::validate_email_address("");
assert!(result.is_err(), "Should fail with empty email");
}
#[test]
fn test_invalid_ip_address() {
let _lock = get_test_lock();
let result = "999.999.999.999".parse::<std::net::IpAddr>();
assert!(result.is_err(), "Should fail with invalid IPv4");
}
#[test]
fn test_cert_expiry_calculation() {
let _lock = get_test_lock();
use fastcert::cert::calculate_cert_expiration;
use time::OffsetDateTime;
let now = OffsetDateTime::now_utc();
let expiration = calculate_cert_expiration();
let diff = (expiration - now).whole_days();
assert!(
(819..=821).contains(&diff),
"Certificate should be valid for approximately 820 days, got {} days",
diff
);
}
#[test]
fn test_cert_expiry_soon() {
let _lock = get_test_lock();
use fastcert::cert::is_cert_expiring_soon;
use time::{Duration, OffsetDateTime};
let now = OffsetDateTime::now_utc();
let expires_soon = now + Duration::days(5);
assert!(
is_cert_expiring_soon(expires_soon),
"Should detect expiring soon"
);
let expires_later = now + Duration::days(50);
assert!(
!is_cert_expiring_soon(expires_later),
"Should not detect as expiring soon"
);
}
#[test]
fn test_certificate_generation_with_email() {
let _lock = get_test_lock();
let temp_dir = TempDir::new().unwrap();
unsafe {
env::set_var("CAROOT", temp_dir.path().to_str().unwrap());
}
let hosts = vec!["user@example.com".to_string()];
let cert_file = temp_dir.path().join("email.pem");
let key_file = temp_dir.path().join("email-key.pem");
let result = fastcert::cert::generate_certificate(
&hosts,
Some(cert_file.to_str().unwrap()),
Some(key_file.to_str().unwrap()),
None,
false,
false,
false,
);
assert!(result.is_ok(), "Should generate cert with email SAN");
unsafe {
env::remove_var("CAROOT");
}
}
#[test]
fn test_certificate_generation_with_uri() {
let _lock = get_test_lock();
let temp_dir = TempDir::new().unwrap();
unsafe {
env::set_var("CAROOT", temp_dir.path().to_str().unwrap());
}
let hosts = vec!["https://example.com/path".to_string()];
let cert_file = temp_dir.path().join("uri.pem");
let key_file = temp_dir.path().join("uri-key.pem");
let result = fastcert::cert::generate_certificate(
&hosts,
Some(cert_file.to_str().unwrap()),
Some(key_file.to_str().unwrap()),
None,
false,
false,
false,
);
assert!(result.is_ok(), "Should generate cert with URI SAN");
unsafe {
env::remove_var("CAROOT");
}
}
#[test]
fn test_serial_number_uniqueness() {
let _lock = get_test_lock();
use fastcert::cert::generate_serial_number;
let mut serials = std::collections::HashSet::new();
for _ in 0..100 {
let serial = generate_serial_number();
assert!(serials.insert(serial), "Serial numbers should be unique");
}
assert_eq!(serials.len(), 100, "Should have 100 unique serials");
}
#[test]
fn test_domain_to_unicode() {
let _lock = get_test_lock();
use fastcert::cert::domain_to_unicode;
let result = domain_to_unicode("example.com");
assert_eq!(result, "example.com", "ASCII should pass through");
let result = domain_to_unicode("xn--e1afmkfd.xn--p1ai");
assert!(!result.is_empty(), "Should convert punycode");
}
#[test]
fn test_pkcs12_generation() {
let _lock = get_test_lock();
let temp_dir = TempDir::new().unwrap();
unsafe {
env::set_var("CAROOT", temp_dir.path().to_str().unwrap());
}
let hosts = vec!["pkcs12.local".to_string()];
let p12_file = temp_dir.path().join("test.p12");
let result = fastcert::cert::generate_certificate(
&hosts,
None,
None,
Some(p12_file.to_str().unwrap()),
false,
false,
true, );
assert!(result.is_ok(), "Should generate PKCS12 file");
assert!(p12_file.exists(), "PKCS12 file should exist");
let metadata = fs::metadata(&p12_file).unwrap();
assert!(metadata.len() > 0, "PKCS12 file should not be empty");
unsafe {
env::remove_var("CAROOT");
}
}
#[test]
fn test_combined_cert_key_file() {
let _lock = get_test_lock();
let temp_dir = TempDir::new().unwrap();
unsafe {
env::set_var("CAROOT", temp_dir.path().to_str().unwrap());
}
let hosts = vec!["combined.local".to_string()];
let combined_file = temp_dir.path().join("combined.pem");
let result = fastcert::cert::generate_certificate(
&hosts,
Some(combined_file.to_str().unwrap()),
Some(combined_file.to_str().unwrap()), None,
false,
false,
false,
);
assert!(result.is_ok(), "Should generate combined cert+key file");
assert!(combined_file.exists(), "Combined file should exist");
let contents = fs::read_to_string(&combined_file).unwrap();
assert!(
contents.contains("BEGIN CERTIFICATE"),
"Should contain certificate"
);
assert!(
contents.contains("BEGIN PRIVATE KEY") || contents.contains("BEGIN RSA PRIVATE KEY"),
"Should contain private key"
);
unsafe {
env::remove_var("CAROOT");
}
}
#[test]
fn test_file_naming_with_port() {
let _lock = get_test_lock();
let temp_dir = TempDir::new().unwrap();
unsafe {
env::set_var("CAROOT", temp_dir.path().to_str().unwrap());
}
let hosts = vec!["example.com:8080".to_string()];
let cert_file = temp_dir.path().join("port-test.pem");
let key_file = temp_dir.path().join("port-test-key.pem");
let result = fastcert::cert::generate_certificate(
&hosts,
Some(cert_file.to_str().unwrap()),
Some(key_file.to_str().unwrap()),
None,
false,
false,
false,
);
assert!(result.is_ok() || result.is_err());
unsafe {
env::remove_var("CAROOT");
}
}
#[test]
fn test_host_type_parsing() {
let _lock = get_test_lock();
use fastcert::cert::HostType;
let result = HostType::parse("example.com");
assert!(matches!(result, Ok(HostType::DnsName(_))));
let result = HostType::parse("192.168.1.1");
assert!(matches!(result, Ok(HostType::IpAddress(_))));
let result = HostType::parse("user@example.com");
assert!(matches!(result, Ok(HostType::Email(_))));
let result = HostType::parse("https://example.com");
assert!(matches!(result, Ok(HostType::Uri(_))));
}