source-map-tauri 0.3.0

Static Tauri app scanner that emits Meilisearch-ready NDJSON
Documentation
use std::fs;

use assert_cmd::Command;
use assert_fs::prelude::*;
use predicates::prelude::*;
use serde_json::Value;

fn binary() -> Command {
    Command::cargo_bin("source-map-tauri").expect("binary exists")
}

#[test]
fn help_lists_main_commands() {
    let mut cmd = binary();
    cmd.arg("--help");
    cmd.assert()
        .success()
        .stdout(predicate::str::contains(
            "Build a searchable source map for Tauri apps",
        ))
        .stdout(predicate::str::contains("scan"))
        .stdout(predicate::str::contains("reindex"))
        .stdout(predicate::str::contains("validate"));
}

#[test]
fn init_creates_default_config_and_ignore_file() {
    let temp = assert_fs::TempDir::new().expect("temp dir");

    let mut cmd = binary();
    cmd.env("HOME", temp.path());
    cmd.args(["init", "--root"])
        .arg(temp.path())
        .assert()
        .success();

    temp.child(".repo-search/tauri/source-map-tauri.toml")
        .assert(predicate::path::exists());
    temp.child(".repo-search/tauri/.gitignore")
        .assert(predicate::path::exists());
    temp.child(".config/meilisearch/connect.json")
        .assert(predicate::path::exists());
}

#[test]
fn scan_fixture_then_validate() {
    let temp = assert_fs::TempDir::new().expect("temp dir");
    let out = temp.child("out");

    let mut scan = binary();
    scan.args([
        "scan",
        "--root",
        "tests/fixtures/tauri-custom-plugin",
        "--repo",
        "fixture",
        "--out",
    ])
    .arg(out.path())
    .assert()
    .success();

    out.child("artifacts.ndjson")
        .assert(predicate::path::exists());
    out.child("edges.ndjson").assert(predicate::path::exists());
    out.child("warnings.ndjson")
        .assert(predicate::path::exists());
    out.child("summary.json").assert(predicate::path::exists());
    out.child("project-info.json")
        .assert(predicate::path::exists());

    let artifacts =
        fs::read_to_string(out.child("artifacts.ndjson").path()).expect("read artifacts");
    let artifact_kinds: Vec<String> = artifacts
        .lines()
        .map(|line| serde_json::from_str::<Value>(line).expect("valid json"))
        .filter_map(|value| value.get("kind").and_then(Value::as_str).map(str::to_owned))
        .collect();

    for kind in [
        "frontend_component",
        "frontend_hook_def",
        "frontend_hook_use",
        "tauri_invoke",
        "tauri_command",
        "tauri_plugin",
        "tauri_plugin_command",
        "tauri_plugin_lifecycle_hook",
        "tauri_permission",
        "tauri_capability",
        "tauri_capability_effective",
        "frontend_test",
        "rust_test",
    ] {
        assert!(
            artifact_kinds.iter().any(|candidate| candidate == kind),
            "missing kind {kind}"
        );
    }

    let mut validate = binary();
    validate.args(["validate", "--input"]).arg(out.path());
    validate.assert().success();
}

#[test]
fn scan_realish_inline_command_fixture() {
    let temp = assert_fs::TempDir::new().expect("temp dir");
    let out = temp.child("out");

    let mut scan = binary();
    scan.args([
        "scan",
        "--root",
        "tests/fixtures/tauri-inline-commands",
        "--repo",
        "realish",
        "--out",
    ])
    .arg(out.path())
    .assert()
    .success();

    let artifacts =
        fs::read_to_string(out.child("artifacts.ndjson").path()).expect("read artifacts");
    let docs: Vec<Value> = artifacts
        .lines()
        .map(|line| serde_json::from_str::<Value>(line).expect("valid json"))
        .collect();

    assert!(docs
        .iter()
        .any(|doc| doc.get("kind").and_then(Value::as_str) == Some("tauri_invoke")));
    assert!(docs
        .iter()
        .any(|doc| doc.get("kind").and_then(Value::as_str) == Some("tauri_command")));
    assert!(docs.iter().any(|doc| {
        doc.get("kind").and_then(Value::as_str) == Some("tauri_capability_effective")
    }));
    assert!(!docs.iter().any(|doc| {
        doc.get("kind").and_then(Value::as_str) == Some("frontend_hook_use")
            && matches!(
                doc.get("name").and_then(Value::as_str),
                Some("useState" | "useEffect" | "useMemo" | "useCallback")
            )
    }));

    let mut validate = binary();
    validate.args(["validate", "--input"]).arg(out.path());
    validate.assert().success();
}

#[test]
fn scan_frontend_http_flow_fixture() {
    let temp = assert_fs::TempDir::new().expect("temp dir");
    let out = temp.child("out");

    let mut scan = binary();
    scan.args([
        "scan",
        "--root",
        "tests/fixtures/frontend-http-flow",
        "--repo",
        "httpflow",
        "--out",
    ])
    .arg(out.path())
    .assert()
    .success();

    let artifacts =
        fs::read_to_string(out.child("artifacts.ndjson").path()).expect("read artifacts");
    let docs: Vec<Value> = artifacts
        .lines()
        .map(|line| serde_json::from_str::<Value>(line).expect("valid json"))
        .collect();

    assert!(docs.iter().any(|doc| {
        doc.get("kind").and_then(Value::as_str) == Some("frontend_api_wrapper")
            && doc.get("name").and_then(Value::as_str) == Some("useLogin")
            && doc.get("normalized_path").and_then(Value::as_str) == Some("/auth/login")
    }));
    assert!(docs.iter().any(|doc| {
        doc.get("kind").and_then(Value::as_str) == Some("frontend_transport")
            && doc.get("name").and_then(Value::as_str) == Some("usePostApi")
    }));
    assert!(docs.iter().any(|doc| {
        doc.get("kind").and_then(Value::as_str) == Some("frontend_http_endpoint")
            && doc.get("name").and_then(Value::as_str) == Some("/auth/login")
    }));

    let flow_docs: Vec<&Value> = docs
        .iter()
        .filter(|doc| {
            doc.get("kind").and_then(Value::as_str) == Some("frontend_http_flow")
                && doc.get("normalized_path").and_then(Value::as_str) == Some("/auth/login")
        })
        .collect();

    assert_eq!(
        flow_docs.len(),
        1,
        "expected exactly one flow for /auth/login"
    );
    let flow = flow_docs[0];
    assert_eq!(
        flow.get("primary_component").and_then(Value::as_str),
        Some("LoginModal")
    );
    assert_eq!(
        flow.get("primary_wrapper").and_then(Value::as_str),
        Some("useLogin")
    );
    assert_eq!(
        flow.get("primary_transport").and_then(Value::as_str),
        Some("usePostApi")
    );

    let suffix_flow_docs: Vec<&Value> = docs
        .iter()
        .filter(|doc| {
            doc.get("kind").and_then(Value::as_str) == Some("frontend_http_flow")
                && doc.get("normalized_path").and_then(Value::as_str)
                    == Some("/appointment/home/search")
        })
        .collect();

    assert_eq!(
        suffix_flow_docs.len(),
        1,
        "expected exactly one flow for /appointment/home/search"
    );
    let suffix_flow = suffix_flow_docs[0];
    assert_eq!(
        suffix_flow.get("primary_wrapper").and_then(Value::as_str),
        Some("useSearchPatientMutation")
    );
    assert_eq!(
        suffix_flow.get("primary_transport").and_then(Value::as_str),
        Some("usePostMutation")
    );
    assert_eq!(
        suffix_flow.get("caller_count").and_then(Value::as_u64),
        Some(2)
    );
    let path_aliases = suffix_flow
        .get("path_aliases")
        .and_then(Value::as_array)
        .expect("path aliases");
    assert!(path_aliases
        .iter()
        .any(|value| value.as_str() == Some("/home/search")));

    let mut validate = binary();
    validate.args(["validate", "--input"]).arg(out.path());
    validate.assert().success();
}