flowmark 0.3.0

A Markdown auto-formatter for clean diffs and semantic line breaks
Documentation
//! CLI integration tests for incremental cache behavior.
#![cfg(feature = "cli")]

use flowmark::incremental_cache::project_manifest_path;
use std::fs;
use std::path::PathBuf;
use std::process::{Command, Output};

fn flowmark_bin() -> PathBuf {
    let mut path = std::env::current_exe().expect("current exe");
    path.pop();
    if path.ends_with("deps") {
        path.pop();
    }
    path.push("flowmark");
    path
}

fn cache_manifest_count(cache_root: &std::path::Path) -> usize {
    let incremental_dir = cache_root.join("incremental");
    if !incremental_dir.exists() {
        return 0;
    }
    fs::read_dir(incremental_dir)
        .expect("read incremental dir")
        .filter_map(Result::ok)
        .filter(|entry| entry.path().is_file())
        .count()
}

fn toml_string_literal(path: &std::path::Path) -> String {
    format!("{:?}", path.to_string_lossy())
}

fn run_flowmark(current_dir: &std::path::Path, args: &[&str]) -> Output {
    Command::new(flowmark_bin()).current_dir(current_dir).args(args).output().expect("run flowmark")
}

#[test]
fn test_incremental_cache_writes_manifest_when_enabled() {
    let project_dir = tempfile::tempdir().expect("create project dir");
    let cache_dir = tempfile::tempdir().expect("create cache dir");
    fs::write(
        project_dir.path().join("doc.md"),
        "# Title\n\nA short paragraph that will stay unchanged.\n",
    )
    .expect("write doc.md");

    let output = run_flowmark(
        project_dir.path(),
        &["--auto", "--cache-dir", cache_dir.path().to_str().expect("cache path to str"), "doc.md"],
    );

    assert!(
        output.status.success(),
        "flowmark should succeed, stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    assert!(
        cache_manifest_count(cache_dir.path()) > 0,
        "expected incremental manifest file when incremental cache is enabled"
    );
}

#[test]
fn test_no_incremental_disables_cache_manifest_creation() {
    let project_dir = tempfile::tempdir().expect("create project dir");
    let cache_dir = tempfile::tempdir().expect("create cache dir");
    fs::write(
        project_dir.path().join("doc.md"),
        "# Title\n\nA short paragraph that will stay unchanged.\n",
    )
    .expect("write doc.md");

    let output = run_flowmark(
        project_dir.path(),
        &[
            "--auto",
            "--no-cache",
            "--cache-dir",
            cache_dir.path().to_str().expect("cache path to str"),
            "doc.md",
        ],
    );

    assert!(
        output.status.success(),
        "flowmark should succeed, stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    assert_eq!(
        cache_manifest_count(cache_dir.path()),
        0,
        "no incremental cache file should be created when --no-cache is set"
    );
}

#[test]
fn test_config_incremental_false_disables_cache_manifest_creation() {
    let project_dir = tempfile::tempdir().expect("create project dir");
    let cache_dir = tempfile::tempdir().expect("create cache dir");
    fs::write(
        project_dir.path().join("flowmark.toml"),
        format!(
            "incremental = false\nincremental-cache-dir = {}\n",
            toml_string_literal(cache_dir.path())
        ),
    )
    .expect("write flowmark.toml");
    fs::write(
        project_dir.path().join("doc.md"),
        "# Title\n\nA short paragraph that will stay unchanged.\n",
    )
    .expect("write doc.md");

    let output = run_flowmark(project_dir.path(), &["--auto", "doc.md"]);

    assert!(
        output.status.success(),
        "flowmark should succeed, stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    assert_eq!(
        cache_manifest_count(cache_dir.path()),
        0,
        "no incremental cache file should be created when config sets incremental = false"
    );
}

#[test]
fn test_config_incremental_cache_dir_is_applied() {
    let project_dir = tempfile::tempdir().expect("create project dir");
    let cache_dir = tempfile::tempdir().expect("create cache dir");
    fs::write(
        project_dir.path().join("flowmark.toml"),
        format!("incremental-cache-dir = {}\n", toml_string_literal(cache_dir.path())),
    )
    .expect("write flowmark.toml");
    fs::write(
        project_dir.path().join("doc.md"),
        "# Title\n\nA short paragraph that will stay unchanged.\n",
    )
    .expect("write doc.md");

    let output = run_flowmark(project_dir.path(), &["--auto", "doc.md"]);

    assert!(
        output.status.success(),
        "flowmark should succeed, stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    assert!(
        cache_manifest_count(cache_dir.path()) > 0,
        "expected incremental manifest in cache dir configured by flowmark.toml"
    );
}

#[test]
fn test_show_cache_reports_usage_without_files() {
    let project_dir = tempfile::tempdir().expect("create project dir");
    let cache_parent = tempfile::tempdir().expect("create cache parent dir");
    let cache_root = cache_parent.path().join("cache-root");
    fs::write(
        project_dir.path().join("doc.md"),
        "# Title\n\nA short paragraph that will stay unchanged.\n",
    )
    .expect("write doc.md");

    let seed = run_flowmark(
        project_dir.path(),
        &["--auto", "--cache-dir", cache_root.to_str().expect("cache path to str"), "doc.md"],
    );
    assert!(
        seed.status.success(),
        "seed run should succeed, stderr: {}",
        String::from_utf8_lossy(&seed.stderr)
    );

    let output = run_flowmark(
        project_dir.path(),
        &["--show-cache", "--cache-dir", cache_root.to_str().expect("cache path to str")],
    );
    assert!(
        output.status.success(),
        "--show-cache should succeed, stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains(&format!("Cache directory: {}", cache_root.display())),
        "show-cache output should include resolved cache directory, got: {stdout}"
    );
    assert!(stdout.contains("Cache files: "), "show-cache output should include file count");
    assert!(stdout.contains("Cache size: "), "show-cache output should include total size");
    assert!(
        stdout.contains("Cache manifests: "),
        "show-cache output should include manifest count"
    );

    let expected_manifest = project_manifest_path(&cache_root, project_dir.path());
    assert!(
        stdout.contains(&format!("Current project manifest: {}", expected_manifest.display())),
        "show-cache output should include current project manifest path, got: {stdout}"
    );
    assert!(
        stdout.contains("Current project entries: 1"),
        "show-cache output should include current project entry count, got: {stdout}"
    );
}

#[test]
fn test_clear_cache_deletes_cache_without_confirmation() {
    let project_dir = tempfile::tempdir().expect("create project dir");
    let cache_parent = tempfile::tempdir().expect("create cache parent dir");
    let cache_root = cache_parent.path().join("cache-root");
    fs::write(
        project_dir.path().join("doc.md"),
        "# Title\n\nA short paragraph that will stay unchanged.\n",
    )
    .expect("write doc.md");

    let seed = run_flowmark(
        project_dir.path(),
        &["--auto", "--cache-dir", cache_root.to_str().expect("cache path to str"), "doc.md"],
    );
    assert!(
        seed.status.success(),
        "seed run should succeed, stderr: {}",
        String::from_utf8_lossy(&seed.stderr)
    );
    assert!(cache_root.exists(), "seed run should create cache root");

    let clear = run_flowmark(
        project_dir.path(),
        &["--clear-cache", "--cache-dir", cache_root.to_str().expect("cache path to str")],
    );
    assert!(
        clear.status.success(),
        "--clear-cache should succeed, stderr: {}",
        String::from_utf8_lossy(&clear.stderr)
    );
    assert!(!cache_root.exists(), "clear-cache should remove cache root");

    let clear_again = run_flowmark(
        project_dir.path(),
        &["--clear-cache", "--cache-dir", cache_root.to_str().expect("cache path to str")],
    );
    assert!(clear_again.status.success(), "second --clear-cache should be idempotent");
    let stdout = String::from_utf8_lossy(&clear_again.stdout);
    assert!(
        stdout.contains("Cache already empty."),
        "second clear should report already empty, got: {stdout}"
    );
}