libbun 0.1.3

Rust facade for hosting JavaScript and TypeScript providers through a replaceable Bun native plugin
Documentation
use libbun::release::{
    LIBBUN_PLUGIN_PATH_ENV, NativePluginResolver, NativePluginSource, RELEASE_TAG,
    current_native_plugin_asset, expected_checksum, verify_file_checksum,
};

#[cfg(feature = "plugin-installer")]
use libbun::release::NativePluginInstall;

#[test]
fn current_asset_names_match_release_contract() {
    let asset = current_native_plugin_asset().expect("current target is supported");
    assert_eq!(asset.release_tag, RELEASE_TAG);
    assert_eq!(asset.repository, "enki/libbun");
    assert_eq!(
        asset.asset_name,
        format!(
            "libbun-plugin-native-{}-{}.tar.zst",
            RELEASE_TAG, asset.target
        )
    );
    assert!(asset.archive_url().ends_with(&asset.asset_name));
    assert_eq!(
        asset.checksums_asset_name(),
        format!("libbun-plugin-native-{}-checksums.txt", RELEASE_TAG)
    );
    if asset.target.ends_with("apple-darwin") {
        assert_eq!(asset.plugin_filename, "liblibbun_plugin_native.dylib");
    } else {
        assert_eq!(asset.plugin_filename, "liblibbun_plugin_native.so");
    }
}

#[test]
fn resolver_prefers_explicit_plugin_path() {
    let asset = current_native_plugin_asset().expect("current target is supported");
    let tempdir = tempfile::tempdir().expect("tempdir");
    let plugin_path = tempdir.path().join(asset.plugin_filename);
    std::fs::write(&plugin_path, b"not a real dynamic library").expect("write plugin placeholder");
    let cached_dir = asset.cache_dir(tempdir.path().join("cache"));
    std::fs::create_dir_all(&cached_dir).expect("create cache dir");
    std::fs::write(cached_dir.join(asset.plugin_filename), b"cached").expect("write cache");

    let resolved = NativePluginResolver::new()
        .with_plugin_path(&plugin_path)
        .with_cache_root(tempdir.path().join("cache"))
        .resolve()
        .expect("explicit plugin path resolves");

    assert_eq!(resolved.path, plugin_path);
    assert_eq!(resolved.source, NativePluginSource::Environment);
}

#[test]
fn resolver_uses_standard_release_cache() {
    let asset = current_native_plugin_asset().expect("current target is supported");
    let tempdir = tempfile::tempdir().expect("tempdir");
    let cached_plugin = asset.cached_plugin_path(tempdir.path());
    std::fs::create_dir_all(cached_plugin.parent().expect("plugin parent")).expect("cache dir");
    std::fs::write(&cached_plugin, b"not a real dynamic library").expect("write plugin");

    let resolved = NativePluginResolver::new()
        .with_cache_root(tempdir.path())
        .with_build_time_plugin(false)
        .resolve()
        .expect("cached plugin resolves");

    assert_eq!(resolved.path, cached_plugin);
    assert_eq!(resolved.source, NativePluginSource::Cache);
}

#[cfg(libbun_download_plugin)]
#[test]
fn resolver_uses_build_time_plugin_after_explicit_path() {
    let build_path =
        libbun::release::build_time_plugin_path().expect("download-plugin emitted plugin path");

    let resolved = NativePluginResolver::new()
        .resolve()
        .expect("build-time plugin resolves");

    assert_eq!(resolved.path, build_path);
    assert_eq!(resolved.source, NativePluginSource::BuildTime);
}

#[test]
fn resolver_missing_plugin_error_is_actionable() {
    let asset = current_native_plugin_asset().expect("current target is supported");
    let tempdir = tempfile::tempdir().expect("tempdir");
    let error = NativePluginResolver::new()
        .with_cache_root(tempdir.path())
        .with_build_time_plugin(false)
        .resolve()
        .expect_err("missing plugin should fail");
    let message = error.to_string();

    assert!(message.contains(LIBBUN_PLUGIN_PATH_ENV), "{message}");
    assert!(message.contains(&asset.asset_name), "{message}");
    assert!(message.contains(&asset.archive_url()), "{message}");
    assert!(message.contains(&asset.target), "{message}");
}

#[test]
fn checksum_helpers_parse_and_verify_release_format() {
    let tempdir = tempfile::tempdir().expect("tempdir");
    let asset_path = tempdir.path().join("asset.txt");
    std::fs::write(&asset_path, b"hello").expect("write asset");
    let checksums = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824  asset.txt\n";

    assert_eq!(
        expected_checksum(checksums, "asset.txt").expect("checksum entry"),
        "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    );
    verify_file_checksum(checksums, "asset.txt", &asset_path).expect("checksum verifies");
}

#[cfg(feature = "plugin-installer")]
#[test]
#[ignore = "downloads and extracts the current GitHub release asset"]
fn installer_downloads_current_release_asset() {
    let asset = current_native_plugin_asset().expect("current target is supported");
    let tempdir = tempfile::tempdir().expect("tempdir");

    let resolved = NativePluginInstall::new()
        .with_cache_root(tempdir.path())
        .install()
        .expect("release asset installs");

    assert_eq!(resolved.source, NativePluginSource::Cache);
    assert_eq!(resolved.path, asset.cached_plugin_path(tempdir.path()));
    assert!(resolved.path.is_file());
    assert!(
        asset
            .cache_dir(tempdir.path())
            .join("checksums.txt")
            .is_file()
    );
    assert!(asset.cache_dir(tempdir.path()).join("SOURCE.txt").is_file());
    assert!(asset.cache_dir(tempdir.path()).join("NOTICE.txt").is_file());
    assert!(
        asset
            .cache_dir(tempdir.path())
            .join("licenses.json")
            .is_file()
    );
}