euv-cli 0.28.26

The official CLI tool for the euv UI framework, providing dev/release profiles with run/build modes, hot reload, and wasm-pack integration.
Documentation
use super::*;

fn mode_args(argv: &[&str]) -> ModeArgs {
    let mut full: Vec<String> = vec!["euv".to_string()];
    for arg in argv {
        full.push((*arg).to_string());
    }
    ModeArgs::parse_from(full)
}

fn scratch(tag: &str) -> PathBuf {
    let dir: PathBuf = env::temp_dir()
        .join("euv-serving-path-tests")
        .join(format!("{tag}-{}", process::id()));
    let _: io::Result<()> = fs::remove_dir_all(&dir);
    fs::create_dir_all(&dir).expect("scratch dir");
    dir
}

fn write_file(path: &Path, contents: &str) {
    if let Some(parent) = path.parent() {
        let _: io::Result<()> = fs::create_dir_all(parent);
    }
    let mut file = fs::File::create(path).expect("scratch file");
    let _: io::Result<()> = io::Write::write_all(&mut file, contents.as_bytes());
}

#[test]
fn the_pkg_dir_is_wherever_the_out_dir_resolves_to() {
    let args: ModeArgs = mode_args(&[]);
    let observed: PathBuf = resolve_pkg_dir(&args);
    assert_eq!(
        observed,
        resolve_out_dir(&args),
        "resolve_pkg_dir is a thin alias over resolve_out_dir and must not drift"
    );
}

#[test]
fn the_pkg_dir_follows_an_explicit_out_dir() {
    let args: ModeArgs = mode_args(&["--crate-path", "app", "--", "--out-dir", "dist"]);
    let observed: PathBuf = resolve_pkg_dir(&args);
    assert!(
        observed.ends_with("dist"),
        "an explicit out dir wins, got {observed:?}"
    );
}

#[test]
fn the_import_path_points_at_the_bundle_from_the_serving_root() {
    let args: ModeArgs = mode_args(&["--crate-path", "app"]);
    let observed: String = resolve_import_path(&args);
    assert!(
        observed.starts_with("./"),
        "a wasm import specifier is relative, got {observed}"
    );
    assert!(
        observed.ends_with(".js"),
        "and names the generated bundle, got {observed}"
    );
}

#[test]
fn the_import_path_of_a_sibling_bundle_never_climbs_out_of_the_serving_root() {
    let args: ModeArgs = mode_args(&["--crate-path", "app", "--", "--out-dir", "dist/pkg"]);
    let observed: String = resolve_import_path(&args);
    assert!(
        !observed.contains(".."),
        "the serving root is chosen as an ancestor of the out dir, so the relative \
         specifier never has to climb out; got {observed}"
    );
    assert!(
        observed.starts_with("./"),
        "and it is still relative, got {observed}"
    );
    assert!(
        observed.ends_with(".js"),
        "and it still names the bundle, got {observed}"
    );
}

#[test]
fn a_file_directly_inside_the_base_is_served() {
    let dir: PathBuf = scratch("inside");
    let block: PathBuf = dir.join("block.js");
    write_file(&block, "export const x = 1;");
    let base: PathBuf = dir.clone();
    let observed: Option<PathBuf> = tokio::runtime::Builder::new_current_thread()
        .build()
        .expect("runtime")
        .block_on(resolve_file_in_base(&base, "block.js"));
    match observed {
        Some(path) => assert_eq!(path, block, "a file inside the base is served"),
        None => panic!("a file inside the base must resolve"),
    }
    let _: io::Result<()> = fs::remove_dir_all(&dir);
}

#[test]
fn a_file_in_a_subdirectory_of_the_base_is_served() {
    let dir: PathBuf = scratch("subdir");
    let block: PathBuf = dir.join("nested").join("deep.js");
    write_file(&block, "export const y = 2;");
    let base: PathBuf = dir.clone();
    let observed: Option<PathBuf> = tokio::runtime::Builder::new_current_thread()
        .build()
        .expect("runtime")
        .block_on(resolve_file_in_base(&base, "nested/deep.js"));
    assert!(
        observed.is_some(),
        "a file below the base is still inside it"
    );
    let _: io::Result<()> = fs::remove_dir_all(&dir);
}

#[test]
fn a_relative_escape_out_of_the_base_is_refused() {
    let dir: PathBuf = scratch("escape");
    let secret: PathBuf = dir.parent().expect("parent").join("euv-secret.txt");
    write_file(&secret, "classified");
    let base: PathBuf = dir.join("public");
    let _: io::Result<()> = fs::create_dir_all(&base);
    let observed: Option<PathBuf> = tokio::runtime::Builder::new_current_thread()
        .build()
        .expect("runtime")
        .block_on(resolve_file_in_base(&base, "../euv-secret.txt"));
    assert_eq!(
        observed, None,
        "a ../ escape must never resolve, or the dev server leaks files above its root"
    );
    let _: io::Result<()> = fs::remove_file(&secret);
    let _: io::Result<()> = fs::remove_dir_all(&dir);
}

#[test]
fn a_deep_relative_escape_is_refused() {
    let dir: PathBuf = scratch("deep-escape");
    let secret: PathBuf = dir.join("secret.txt");
    write_file(&secret, "classified");
    let base: PathBuf = dir.join("a").join("b");
    let _: io::Result<()> = fs::create_dir_all(&base);
    let observed: Option<PathBuf> = tokio::runtime::Builder::new_current_thread()
        .build()
        .expect("runtime")
        .block_on(resolve_file_in_base(&base, "../../secret.txt"));
    assert_eq!(
        observed, None,
        "a multi-level climb out of the base is refused too"
    );
    let _: io::Result<()> = fs::remove_dir_all(&dir);
}

#[test]
fn a_missing_file_is_not_served() {
    let dir: PathBuf = scratch("missing");
    let base: PathBuf = dir.clone();
    let observed: Option<PathBuf> = tokio::runtime::Builder::new_current_thread()
        .build()
        .expect("runtime")
        .block_on(resolve_file_in_base(&base, "nothing-here.js"));
    assert_eq!(
        observed, None,
        "a path that does not exist resolves to nothing"
    );
    let _: io::Result<()> = fs::remove_dir_all(&dir);
}

#[test]
fn an_absolute_request_path_cannot_escape_the_base() {
    let dir: PathBuf = scratch("absolute");
    let secret: PathBuf = dir.join("secret.txt");
    write_file(&secret, "classified");
    let base: PathBuf = dir.join("public");
    let _: io::Result<()> = fs::create_dir_all(&base);
    let requested: String = secret.to_string_lossy().to_string();
    let observed: Option<PathBuf> = tokio::runtime::Builder::new_current_thread()
        .build()
        .expect("runtime")
        .block_on(resolve_file_in_base(&base, &requested));
    assert_eq!(
        observed, None,
        "an absolute path outside the base is refused even though it exists"
    );
    let _: io::Result<()> = fs::remove_dir_all(&dir);
}

#[test]
fn a_dot_segment_request_still_resolves_inside_the_base() {
    let dir: PathBuf = scratch("dot-segment");
    let block: PathBuf = dir.join("block.js");
    write_file(&block, "export const z = 3;");
    let base: PathBuf = dir.clone();
    let observed: Option<PathBuf> = tokio::runtime::Builder::new_current_thread()
        .build()
        .expect("runtime")
        .block_on(resolve_file_in_base(&base, "./block.js"));
    assert!(
        observed.is_some(),
        "a leading ./ stays inside the base and must not be treated as an escape"
    );
    let _: io::Result<()> = fs::remove_dir_all(&dir);
}