certmagic 0.2.0

Automatic TLS certificate acquisition, renewal, and maintenance for Rust servers
Documentation
//! Compile-level coverage for manager builder inputs.

#![cfg(feature = "file-storage")]

use std::sync::Arc;

use certmagic::storage::FileStorage;
use certmagic::{Cache, CacheOptions, ConfigBuilder, MaintenanceConfig, OnDemandConfig, Policy};
use std::time::Duration;
use tokio_util::sync::CancellationToken;

#[tokio::test]
async fn builder_accepts_shared_cache_and_on_demand_config() {
    let cache = Cache::new(CacheOptions::default()).unwrap();
    let config = ConfigBuilder::new()
        .cache(Arc::clone(&cache))
        .on_demand(Arc::new(OnDemandConfig::default()))
        .build()
        .unwrap();

    assert!(Arc::ptr_eq(config.cache(), &cache));
    assert!(config.options.on_demand.is_some());
}

#[tokio::test]
async fn on_demand_sync_decision_is_an_admission_gate() {
    let config =
        OnDemandConfig::default().with_sync_decision_func(|name| name.ends_with(".example.com"));
    let decision = config.decision_func.expect("sync builder installs a gate");
    let ct = CancellationToken::new();

    assert!(
        decision(ct.clone(), "allowed.example.com".into())
            .await
            .is_ok()
    );
    assert!(decision(ct, "outside.example.net".into()).await.is_err());
}

#[tokio::test]
async fn maintenance_config_bridges_intervals_storage_and_ocsp_policy() {
    let cache = Cache::new_without_maintenance(CacheOptions::default()).unwrap();
    let root = tempfile::tempdir().unwrap();
    let storage = FileStorage::new(root.path());
    let maintenance = MaintenanceConfig::new(storage)
        .with_renew_check_interval(Duration::from_secs(17))
        .with_ocsp_check_interval(Duration::from_secs(29));

    let config = ConfigBuilder::new()
        .cache(Arc::clone(&cache))
        .maintenance(maintenance)
        .policy(Policy::default().with_must_staple(true))
        .build()
        .unwrap();

    assert_eq!(
        cache.options().renew_check_interval,
        Some(Duration::from_secs(17))
    );
    assert_eq!(
        cache.options().ocsp_check_interval,
        Some(Duration::from_secs(29))
    );
    assert!(config.options.storage.is_some());
    assert_eq!(config.options.ocsp, certmagic::OcspConfig::default());
    assert!(config.options.must_staple);
}

#[tokio::test]
async fn builder_storage_precedence_follows_last_storage_setter() {
    let root_a = tempfile::tempdir().unwrap();
    let root_b = tempfile::tempdir().unwrap();
    let storage_a: Arc<dyn certmagic::storage::Storage> = FileStorage::new(root_a.path());
    let storage_b: Arc<dyn certmagic::storage::Storage> = FileStorage::new(root_b.path());

    let first = ConfigBuilder::new()
        .storage(Arc::clone(&storage_a))
        .maintenance(MaintenanceConfig::new(Arc::clone(&storage_b)))
        .build()
        .unwrap();
    assert!(Arc::ptr_eq(&first.ground_truth_storage(), &storage_b));
    first.cache().stop_and_wait().await;

    let second = ConfigBuilder::new()
        .maintenance(MaintenanceConfig::new(Arc::clone(&storage_b)))
        .storage(Arc::clone(&storage_a))
        .build()
        .unwrap();
    assert!(Arc::ptr_eq(&second.ground_truth_storage(), &storage_a));
    second.cache().stop_and_wait().await;
}

#[tokio::test]
async fn builder_created_cache_starts_after_config_binding_but_shared_cache_keeps_lifecycle() {
    let owned = ConfigBuilder::new().build().unwrap();
    assert!(owned.cache().maintenance_running());
    owned.cache().stop_and_wait().await;

    let shared = Cache::new_without_maintenance(CacheOptions::default()).unwrap();
    let config = ConfigBuilder::new()
        .cache(Arc::clone(&shared))
        .build()
        .unwrap();
    assert!(!shared.maintenance_running());
    config.cache().stop_and_wait().await;
}