omena-bridge 0.2.0

CME-coupled bridge crate for Omena semantic graph inputs
Documentation
use std::{fs, path::PathBuf, time::SystemTime};

use super::*;

type TestResult<T = ()> = Result<T, Box<dyn std::error::Error>>;

#[test]
fn extracts_vite_object_aliases_from_define_config() -> Result<(), Box<dyn std::error::Error>> {
    let root = temp_dir("omena_bridge_vite_alias_define_config")?;
    let config_path = root.join("vite.config.ts");
    let source = r#"
        import { defineConfig } from "vite";
        export default defineConfig({
          resolve: {
            alias: {
              "@styles": "./src/styles",
              "@root": path.resolve(__dirname, "src")
            }
          }
        });
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.unrecognized, Vec::new());
    assert_eq!(summary.aliases.len(), 2);
    assert_eq!(summary.aliases[0].pattern, "@styles");
    assert_eq!(
        summary.aliases[0].target_path,
        root.join("src/styles").to_string_lossy()
    );
    assert_eq!(summary.aliases[1].pattern, "@root");
    assert_eq!(
        summary.aliases[1].target_path,
        root.join("src").to_string_lossy()
    );
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn extracts_webpack_array_aliases_from_module_exports() -> Result<(), Box<dyn std::error::Error>> {
    let root = temp_dir("omena_bridge_webpack_alias_array")?;
    let config_path = root.join("webpack.config.js");
    let source = r#"
        module.exports = {
          resolve: {
            alias: [
              { find: "@theme", replacement: "./src/theme" }
            ]
          }
        };
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.unrecognized, Vec::new());
    assert_eq!(summary.aliases.len(), 1);
    assert_eq!(summary.aliases[0].pattern, "@theme");
    assert_eq!(
        summary.aliases[0].target_path,
        root.join("src/theme").to_string_lossy()
    );
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn preserves_webpack_array_alias_declaration_order() -> Result<(), Box<dyn std::error::Error>> {
    let root = temp_dir("omena_bridge_webpack_alias_array_order")?;
    let config_path = root.join("webpack.config.js");
    let source = r#"
        module.exports = {
          resolve: {
            alias: [
              { find: "@theme", replacement: "./src/first" },
              { find: "@theme", replacement: "./src/second" }
            ]
          }
        };
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.unrecognized, Vec::new());
    assert_eq!(summary.aliases.len(), 2);
    assert_eq!(summary.aliases[0].pattern, "@theme");
    assert_eq!(
        summary.aliases[0].target_path,
        root.join("src/first").to_string_lossy()
    );
    assert_eq!(summary.aliases[1].pattern, "@theme");
    assert_eq!(
        summary.aliases[1].target_path,
        root.join("src/second").to_string_lossy()
    );
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn extracts_vite_aliases_from_top_level_config_identifier() -> TestResult {
    let root = temp_dir("omena_bridge_vite_alias_config_identifier")?;
    let config_path = root.join("vite.config.ts");
    let source = r#"
        import { defineConfig } from "vite";
        const config = {
          resolve: {
            alias: {
              "@shared": "./src/shared"
            }
          }
        };
        export default defineConfig(config);
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.unrecognized, Vec::new());
    assert_eq!(summary.aliases.len(), 1);
    assert_eq!(summary.aliases[0].pattern, "@shared");
    assert_eq!(
        summary.aliases[0].target_path,
        root.join("src/shared").to_string_lossy()
    );
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn extracts_object_aliases_from_top_level_alias_identifier() -> TestResult {
    let root = temp_dir("omena_bridge_vite_alias_object_identifier")?;
    let config_path = root.join("vite.config.ts");
    let source = r#"
        const aliases = {
          "@tokens": "./src/tokens"
        };
        export default {
          resolve: {
            alias: aliases
          }
        };
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.unrecognized, Vec::new());
    assert_eq!(summary.aliases.len(), 1);
    assert_eq!(summary.aliases[0].pattern, "@tokens");
    assert_eq!(
        summary.aliases[0].target_path,
        root.join("src/tokens").to_string_lossy()
    );
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn extracts_array_aliases_from_top_level_alias_identifier() -> TestResult {
    let root = temp_dir("omena_bridge_webpack_alias_array_identifier")?;
    let config_path = root.join("webpack.config.js");
    let source = r#"
        const aliases = [
          { find: "@icons", replacement: "./src/icons" }
        ];
        module.exports = {
          resolve: {
            alias: aliases
          }
        };
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.unrecognized, Vec::new());
    assert_eq!(summary.aliases.len(), 1);
    assert_eq!(summary.aliases[0].pattern, "@icons");
    assert_eq!(
        summary.aliases[0].target_path,
        root.join("src/icons").to_string_lossy()
    );
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn extracts_aliases_from_top_level_resolve_identifier() -> TestResult {
    let root = temp_dir("omena_bridge_vite_resolve_identifier")?;
    let config_path = root.join("vite.config.ts");
    let source = r#"
        const resolver = {
          alias: {
            "@ui": "./src/ui"
          }
        };
        export default {
          resolve: resolver
        };
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.unrecognized, Vec::new());
    assert_eq!(summary.aliases.len(), 1);
    assert_eq!(summary.aliases[0].pattern, "@ui");
    assert_eq!(
        summary.aliases[0].target_path,
        root.join("src/ui").to_string_lossy()
    );
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn marks_dynamic_resolve_identifier_unrecognized() -> TestResult {
    let root = temp_dir("omena_bridge_dynamic_resolve_identifier")?;
    let config_path = root.join("vite.config.ts");
    let source = r#"
        const resolver = buildResolver();
        export default {
          resolve: resolver
        };
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.aliases, Vec::new());
    assert_eq!(summary.unrecognized.len(), 1);
    assert_eq!(summary.unrecognized[0].reason, "dynamic-alias-container");
    assert_eq!(summary.unrecognized[0].text, "resolver");
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn marks_dynamic_alias_identifier_unrecognized() -> TestResult {
    let root = temp_dir("omena_bridge_alias_dynamic_identifier")?;
    let config_path = root.join("vite.config.ts");
    let source = r#"
        const aliases = buildAliases();
        export default {
          resolve: {
            alias: aliases
          }
        };
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.aliases, Vec::new());
    assert_eq!(summary.unrecognized.len(), 1);
    assert_eq!(summary.unrecognized[0].reason, "dynamic-alias-container");
    assert_eq!(summary.unrecognized[0].text, "aliases");
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn marks_dynamic_alias_entries_unrecognized() -> Result<(), Box<dyn std::error::Error>> {
    let root = temp_dir("omena_bridge_vite_alias_dynamic")?;
    let config_path = root.join("vite.config.ts");
    let source = r#"
        export default {
          resolve: {
            alias: [{ find: /^@dynamic/, replacement: dynamicTarget }]
          }
        };
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.aliases, Vec::new());
    assert!(
        summary
            .unrecognized
            .iter()
            .any(|entry| entry.reason == "regex-alias-find")
    );
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn marks_dynamic_exported_config_unrecognized_without_top_level_fallback()
-> Result<(), Box<dyn std::error::Error>> {
    let root = temp_dir("omena_bridge_vite_dynamic_export")?;
    let config_path = root.join("vite.config.ts");
    let source = r#"
        const unrelated = {
          resolve: {
            alias: { "@wrong": "./src/wrong" }
          }
        };
        export default defineConfig(({ mode }) => ({
          resolve: {
            alias: { "@styles": "./src/styles" }
          }
        }));
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.aliases, Vec::new());
    assert_eq!(summary.unrecognized.len(), 1);
    assert_eq!(summary.unrecognized[0].reason, "dynamic-config-export");
    assert!(summary.unrecognized[0].text.contains("defineConfig"));
    assert!(!summary.unrecognized[0].text.contains("@wrong"));
    let _ = fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn marks_dynamic_module_exports_config_unrecognized() -> Result<(), Box<dyn std::error::Error>> {
    let root = temp_dir("omena_bridge_webpack_dynamic_export")?;
    let config_path = root.join("webpack.config.cjs");
    let source = r#"
        module.exports = (env) => ({
          resolve: {
            alias: { "@theme": env.themePath }
          }
        });
    "#;

    let summary =
        summarize_omena_bridge_bundler_path_aliases_for_config(config_path.as_path(), source);

    assert_eq!(summary.aliases, Vec::new());
    assert_eq!(summary.unrecognized.len(), 1);
    assert_eq!(summary.unrecognized[0].reason, "dynamic-config-export");
    assert!(summary.unrecognized[0].text.contains("env"));
    let _ = fs::remove_dir_all(root);
    Ok(())
}

fn temp_dir(prefix: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
    let suffix = SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)?
        .as_nanos();
    let path = std::env::temp_dir().join(format!("{prefix}_{suffix}"));
    fs::create_dir_all(path.as_path())?;
    Ok(path)
}