rebecca-core 0.3.0

Core planning, safety, scanning, and history models for Rebecca.
Documentation
use std::path::Path;

use rebecca_core::disk_map::{
    DiskMapEntryKind, DiskMapGroupKind, DiskMapRequest, DiskMapSortField, inspect_map,
};
use rebecca_core::disk_session::{DiskMapDistributionFilter, DiskMapSession, DiskMapSessionFilter};
use rebecca_core::scan::{ScanBackendKind, ScanCancellationToken};

fn write_file(path: impl AsRef<Path>, bytes: &[u8]) {
    let path = path.as_ref();
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent).unwrap();
    }
    std::fs::write(path, bytes).unwrap();
}

#[test]
fn disk_session_preserves_root_metrics_and_navigates_children() {
    let temp = tempfile::tempdir().unwrap();
    let root = temp.path().join("workspace");
    write_file(root.join("big").join("data.bin"), b"abcdef");
    write_file(root.join("small.txt"), b"x");

    let report = inspect_map(
        &DiskMapRequest::new(vec![root.clone()])
            .with_scan_backend(ScanBackendKind::PortableRecursive)
            .with_top_limit(10),
        &ScanCancellationToken::new(),
    )
    .unwrap();
    let session = DiskMapSession::from_report(report);

    let root_id = session.root_ids()[0];
    let root_node = session.node(root_id).unwrap();
    assert_eq!(root_node.path, root);
    assert_eq!(root_node.metrics.logical_bytes, 7);

    let rows = session.visible_rows(
        Some(root_id),
        DiskMapSortField::Logical,
        DiskMapSessionFilter::default(),
    );
    assert_eq!(rows[0].name, "big");
    assert_eq!(rows[0].metrics.logical_bytes, 6);
    assert_eq!(rows[1].name, "small.txt");
    assert_eq!(rows[1].metrics.logical_bytes, 1);
}

#[test]
fn disk_session_reconstructs_visible_parent_chain_from_ranked_entries() {
    let temp = tempfile::tempdir().unwrap();
    let root = temp.path().join("workspace");
    write_file(
        root.join("parent").join("child").join("large.bin"),
        b"abcdef",
    );

    let report = inspect_map(
        &DiskMapRequest::new(vec![root.clone()])
            .with_scan_backend(ScanBackendKind::PortableRecursive)
            .with_entry_kind(Some(DiskMapEntryKind::File))
            .with_top_limit(1),
        &ScanCancellationToken::new(),
    )
    .unwrap();
    assert_eq!(report.top_entries.len(), 1);

    let session = DiskMapSession::from_report(report);
    let root_id = session.root_ids()[0];
    let rows = session.visible_rows(
        Some(root_id),
        DiskMapSortField::Logical,
        DiskMapSessionFilter::default(),
    );

    assert_eq!(rows.len(), 1);
    assert_eq!(rows[0].name, "parent");
    assert!(rows[0].synthetic);
    assert!(rows[0].has_children);
}

#[test]
fn disk_session_filters_rows_by_path_text() {
    let temp = tempfile::tempdir().unwrap();
    let root = temp.path().join("workspace");
    write_file(root.join("target").join("data.bin"), b"abc");
    write_file(root.join("other").join("data.bin"), b"abc");

    let report = inspect_map(
        &DiskMapRequest::new(vec![root])
            .with_scan_backend(ScanBackendKind::PortableRecursive)
            .with_top_limit(10),
        &ScanCancellationToken::new(),
    )
    .unwrap();
    let session = DiskMapSession::from_report(report);
    let root_id = session.root_ids()[0];
    let rows = session.visible_rows(
        Some(root_id),
        DiskMapSortField::Logical,
        DiskMapSessionFilter {
            path_contains: Some("target"),
            ..DiskMapSessionFilter::default()
        },
    );

    assert_eq!(rows.len(), 1);
    assert_eq!(rows[0].name, "target");
}

#[test]
fn disk_session_filters_rows_by_kind_and_extension() {
    let temp = tempfile::tempdir().unwrap();
    let root = temp.path().join("workspace");
    write_file(root.join("cache.tmp"), b"abc");
    write_file(root.join("log.txt"), b"abc");
    write_file(root.join("nested").join("ignored.tmp"), b"abc");

    let report = inspect_map(
        &DiskMapRequest::new(vec![root])
            .with_scan_backend(ScanBackendKind::PortableRecursive)
            .with_top_limit(10),
        &ScanCancellationToken::new(),
    )
    .unwrap();
    let session = DiskMapSession::from_report(report);
    let root_id = session.root_ids()[0];

    let rows = session.visible_rows(
        Some(root_id),
        DiskMapSortField::Logical,
        DiskMapSessionFilter {
            path_contains: None,
            entry_kind: Some(DiskMapEntryKind::File),
            extension: Some(".tmp"),
        },
    );

    assert_eq!(rows.len(), 1);
    assert_eq!(rows[0].name, "cache.tmp");
}

#[test]
fn disk_session_restores_node_identity_by_path() {
    let temp = tempfile::tempdir().unwrap();
    let root = temp.path().join("workspace");
    let parent = root.join("target");
    let child = parent.join("data.bin");
    write_file(&child, b"abc");

    let report = inspect_map(
        &DiskMapRequest::new(vec![root.clone()])
            .with_scan_backend(ScanBackendKind::PortableRecursive)
            .with_top_limit(10),
        &ScanCancellationToken::new(),
    )
    .unwrap();
    let session = DiskMapSession::from_report(report);

    let parent_id = session.node_id_by_path(&parent).expect("parent id");
    assert_eq!(session.node_path(parent_id), Some(parent.as_path()));
    assert_eq!(
        session.nearest_existing_ancestor(parent.join("missing.bin")),
        Some(parent_id)
    );
    assert_eq!(
        session.restore_parent_by_path(Some(&parent)),
        Some(parent_id)
    );
    assert_eq!(
        session.restore_parent_by_path(Some(&root.join("missing"))),
        session.root_ids().first().copied()
    );
    assert!(session.node_id_by_path(root.join("missing")).is_none());
}

#[test]
fn disk_session_preserves_extension_distribution_rows() {
    let temp = tempfile::tempdir().unwrap();
    let root = temp.path().join("workspace");
    write_file(root.join("src").join("main.rs"), b"abc");
    write_file(root.join("docs").join("readme.md"), b"abcde");
    write_file(root.join("LICENSE"), b"xy");

    let report = inspect_map(
        &DiskMapRequest::new(vec![root])
            .with_scan_backend(ScanBackendKind::PortableRecursive)
            .with_top_limit(10)
            .with_group_kinds(vec![DiskMapGroupKind::Extension])
            .with_group_limit(10),
        &ScanCancellationToken::new(),
    )
    .unwrap();
    let session = DiskMapSession::from_report(report);
    let rows = session.distribution_rows(
        DiskMapGroupKind::Extension,
        DiskMapSortField::Logical,
        DiskMapDistributionFilter::default(),
    );

    assert_eq!(
        rows.iter()
            .map(|row| (
                row.key.as_str(),
                row.label.as_str(),
                row.metrics.logical_bytes
            ))
            .collect::<Vec<_>>(),
        vec![
            (".md", ".md", 5),
            (".rs", ".rs", 3),
            ("[no-extension]", "No extension", 2),
        ]
    );
    assert_eq!(rows.iter().map(|row| row.metrics.files).sum::<u64>(), 3);
}