vacuna 0.4.2

Simple web server for static files
Documentation
use std::fs::read;

use rustls_acme::CertCache;

use crate::{
    config::Path,
    server::{self, PathTree, read_paths},
};

#[test]
fn test_path_tree() {
    let _ = pretty_env_logger::try_init();
    let mut paths = Vec::new();
    paths.push(Path::new("/test/path/one/two", "a location"));
    paths.push(Path::new("/test/path/one/one", "a location"));
    paths.push(Path::new("/test/path/other", "a location"));
    paths.push(Path::new("/different/path/", "a location"));

    let read = read_paths(&paths);

    println!("res: {:#?}", read);

    match &read[0] {
        PathTree::Group(paths) => {
            assert_eq!(paths.path, "/test/path/");
            assert_eq!(paths.children.len(), 2);
            match &paths.children[0] {
                PathTree::Path(path) => {
                    assert_eq!(path.path, "other");
                }
                _ => panic!("wrong conversion"),
            }

            match &paths.children[1] {
                PathTree::Group(up) => {
                    assert_eq!(up.path, "one/");
                    assert_eq!(up.children.len(), 2);
                }
                _ => panic!("wrong conversion"),
            }
        }
        _ => panic!("wrong conversion"),
    }

    match &read[1] {
        PathTree::Path(path) => {
            assert_eq!(path.path, "/different/path/");
        }
        _ => panic!("wrong conversion"),
    }
}

#[test]
fn test_path_tree_double_jump() {
    let _ = pretty_env_logger::try_init();
    let mut paths = Vec::new();
    paths.push(Path::new("/test/path/other", "a location"));
    paths.push(Path::new("/test/path/one/two", "a location"));
    paths.push(Path::new("/test/path/one/one", "a location"));
    paths.push(Path::new("/test/one", "a location"));

    let read = read_paths(&paths);

    println!("res: {:#?}", read);

    match &read[0] {
        PathTree::Group(paths) => {
            assert_eq!(paths.path, "/test/");
            assert_eq!(paths.children.len(), 2);

            match &paths.children[0] {
                PathTree::Group(up) => {
                    assert_eq!(up.path, "path/");
                    assert_eq!(up.children.len(), 2);
                }
                _ => panic!("wrong conversion"),
            }
            match &paths.children[1] {
                PathTree::Path(path) => {
                    assert_eq!(path.path, "one");
                }
                _ => panic!("wrong conversion"),
            }
        }
        _ => panic!("wrong conversion"),
    }
}

#[test]
fn test_path_tree_simple_one_after_the_other() {
    let _ = pretty_env_logger::try_init();
    let mut paths = Vec::new();
    paths.push(Path::new("/test/one/", "a location"));
    paths.push(Path::new("/test", "a location"));
    paths.push(Path::new("/other", "a location"));

    let read = read_paths(&paths);

    println!("res: {:#?}", read);

    match &read[0] {
        PathTree::Group(paths) => {
            assert_eq!(paths.path, "/test");
            assert_eq!(paths.children.len(), 2);

            match &paths.children[0] {
                PathTree::Path(up) => {
                    assert_eq!(up.path, "/one/");
                }
                _ => panic!("wrong conversion"),
            }
            match &paths.children[1] {
                PathTree::Path(path) => {
                    assert_eq!(path.path, "");
                }
                _ => panic!("wrong conversion"),
            }
        }
        _ => panic!("wrong conversion"),
    }

    match &read[1] {
        PathTree::Path(path) => {
            assert_eq!(path.path, "/other");
        }
        _ => panic!("wrong conversion"),
    }
}

#[actix_rt::test]
async fn test_cert_read_write() {
    use server::FilesCache;
    let cache = FilesCache::new("target/certs").unwrap();
    let data = read("./fixtures/all.pem").unwrap();
    let domains = ["example.com".to_owned()];

    // Parse original data to understand the expected structure
    let pem_blocks = pem::parse_many(&data).unwrap();

    // The load_cert function returns certificates first, then private key
    let mut cert_pems = Vec::new();
    let mut key_pem = None;

    for pem in &pem_blocks {
        match pem.tag() {
            "CERTIFICATE" => cert_pems.push(pem.clone()),
            "PRIVATE KEY" | "RSA PRIVATE KEY" | "EC PRIVATE KEY" => {
                if key_pem.is_none() {
                    key_pem = Some(pem.clone());
                }
            }
            _ => {} // Ignore other block types
        }
    }

    cache.store_cert(&domains, "example.com", &data).await.unwrap();

    let read = cache.load_cert(&domains, "example.com").await.unwrap();
    match read {
        Some(cert_data) => {
            assert_eq!(
                String::from_utf8(data),
                String::from_utf8(cert_data),
                "Read cert data should exactly match stored data"
            );
        }
        None => {
            panic!("Failed to read cert after storing");
        }
    }
}

#[actix_rt::test]
async fn test_domain_links_creation() {
    use server::FilesCache;
    use std::fs;
    use std::path::Path;

    let test_dir = "target/certs_test_links";
    let cache = FilesCache::new(test_dir).unwrap();
    let data = read("./fixtures/all.pem").unwrap();
    let domains = vec!["example.com".to_owned(), "www.example.com".to_owned()];

    // Clean up any existing test directory
    if Path::new(test_dir).exists() {
        fs::remove_dir_all(test_dir).unwrap();
    }

    // Store certificate which should create domain links
    cache.store_cert(&domains, "example.com", &data).await.unwrap();

    // Check that symbolic links were created for each domain
    for domain in &domains {
        let link_path = Path::new(test_dir).join(domain);
        assert!(link_path.is_symlink(), "Path should be a symbolic link: {}", domain);

        // Verify the link points to a valid certificate directory
        let link_target = fs::read_link(&link_path).unwrap();

        let cert_dir = if link_target.is_absolute() {
            link_target.clone()
        } else {
            Path::new(test_dir).join(&link_target)
        };
        assert!(cert_dir.exists(), "Link target should exist: {}", cert_dir.display());

        // Verify certificate files exist in the linked directory
        assert!(
            cert_dir.join("fullchain.pem").exists(),
            "fullchain.pem should exist in cert directory"
        );
        assert!(
            cert_dir.join("privkey.pem").exists(),
            "privkey.pem should exist in cert directory"
        );
    }

    // Test updating links with new certificate (should replace old links)
    let domains2 = vec!["newdomain.com".to_owned()];
    cache.store_cert(&domains2, "newdomain.com", &data).await.unwrap();

    let new_link_path = Path::new(test_dir).join("newdomain.com");
    assert!(new_link_path.exists(), "New domain link should exist");
    assert!(new_link_path.is_symlink(), "New domain path should be a symbolic link");

    // Clean up
    fs::remove_dir_all(test_dir).unwrap();
}