batpak 0.8.2

Event sourcing with causal graphs and caller-defined gates. Sync API, no async runtime.
Documentation
use super::{StoreError, StoreLockMode};
use std::error::Error as _;
use std::io;

fn assert_display_contains(error: &StoreError, needle: &str) {
    let display = error.to_string();
    assert!(
        display.contains(needle),
        "helper constructor display should contain {needle:?}, got {display:?}"
    );
}

#[test]
fn batch_failed_helper_preserves_item_index_and_source() {
    let error = StoreError::batch_failed(
        3,
        StoreError::Io(io::Error::new(io::ErrorKind::TimedOut, "append timed out")),
    );

    assert!(matches!(
        &error,
        StoreError::BatchFailed {
            item_index: 3,
            source
        } if matches!(source.as_ref(), StoreError::Io(_))
    ));
    assert_display_contains(&error, "batch failed at item 3");
    assert_display_contains(&error, "append timed out");
    assert!(
        error
            .source()
            .is_some_and(|source| source.to_string().contains("append timed out")),
        "BatchFailed helper should expose the wrapped StoreError as source"
    );
}

#[test]
fn batch_sync_failed_helper_preserves_count_and_source() {
    let error = StoreError::batch_sync_failed(4, StoreError::Io(io::Error::other("fsync failed")));

    assert!(matches!(
        &error,
        StoreError::BatchSyncFailed {
            item_count: 4,
            source
        } if matches!(source.as_ref(), StoreError::Io(_))
    ));
    assert_display_contains(&error, "batch sync failed after writing 4 items");
    assert_display_contains(&error, "fsync failed");
    assert!(
        error
            .source()
            .is_some_and(|source| source.to_string().contains("fsync failed")),
        "BatchSyncFailed helper should expose the wrapped StoreError as source"
    );
}

#[test]
fn corrupt_magic_helper_builds_corrupt_segment() {
    let error = StoreError::corrupt_magic(9);

    assert!(
        matches!(
            error,
            StoreError::CorruptSegment {
                segment_id: 9,
                ref detail
            } if detail == "bad magic"
        ),
        "expected bad-magic CorruptSegment, got {error:?}"
    );
    assert_display_contains(&error, "corrupt segment 9");
    assert_display_contains(&error, "bad magic");
    assert!(error.source().is_none());
}

#[test]
fn corrupt_eof_helper_builds_corrupt_segment() {
    let error = StoreError::corrupt_eof(11);

    assert!(
        matches!(
            error,
            StoreError::CorruptSegment {
                segment_id: 11,
                ref detail
            } if detail == "unexpected EOF during read"
        ),
        "expected EOF CorruptSegment, got {error:?}"
    );
    assert_display_contains(&error, "corrupt segment 11");
    assert_display_contains(&error, "unexpected EOF during read");
    assert!(error.source().is_none());
}

#[test]
fn corrupt_version_helper_builds_corrupt_segment() {
    let error = StoreError::corrupt_version(12, 99);

    assert!(
        matches!(
            error,
            StoreError::CorruptSegment {
                segment_id: 12,
                ref detail
            } if detail.contains("unsupported segment version: 99")
        ),
        "expected version CorruptSegment, got {error:?}"
    );
    assert_display_contains(&error, "corrupt segment 12");
    assert_display_contains(&error, "unsupported segment version: 99");
    assert!(error.source().is_none());
}

#[test]
fn cache_msg_helper_builds_cache_failed_without_typed_source() {
    let error = StoreError::cache_msg("cache metadata short read");

    assert!(matches!(error, StoreError::CacheFailed(_)));
    assert_display_contains(&error, "cache error");
    assert_display_contains(&error, "cache metadata short read");
    assert!(
        error
            .source()
            .is_some_and(|source| source.to_string().contains("cache metadata short read")),
        "CacheFailed helper should expose the boxed message error as source"
    );
}

#[test]
fn cache_error_helper_builds_cache_failed_with_typed_source() {
    let error = StoreError::cache_error(io::Error::new(
        io::ErrorKind::PermissionDenied,
        "cache dir denied",
    ));

    assert!(matches!(error, StoreError::CacheFailed(_)));
    assert_display_contains(&error, "cache error");
    assert_display_contains(&error, "cache dir denied");
    assert!(
        error
            .source()
            .is_some_and(|source| source.to_string().contains("cache dir denied")),
        "CacheFailed typed helper should expose the wrapped source"
    );
}

#[test]
fn ser_msg_helper_builds_serialization_error() {
    let error = StoreError::ser_msg("frame exceeds u32::MAX");

    assert!(matches!(error, StoreError::Serialization(_)));
    assert_display_contains(&error, "serialization error");
    assert_display_contains(&error, "frame exceeds u32::MAX");
    assert!(
        error
            .source()
            .is_some_and(|source| source.to_string().contains("frame exceeds u32::MAX")),
        "Serialization helper should expose the boxed message error as source"
    );
}

#[test]
fn corrupt_segment_with_detail_helper_builds_corrupt_segment() {
    let error = StoreError::corrupt_segment_with_detail(13, "valid CRC but malformed msgpack");

    assert!(
        matches!(
            error,
            StoreError::CorruptSegment {
                segment_id: 13,
                ref detail
            } if detail == "valid CRC but malformed msgpack"
        ),
        "expected detail-preserving CorruptSegment, got {error:?}"
    );
    assert_display_contains(&error, "corrupt segment 13");
    assert_display_contains(&error, "valid CRC but malformed msgpack");
    assert!(error.source().is_none());
}

#[test]
fn store_locked_display_names_modes() {
    let read_only = StoreError::StoreLocked {
        path: "fixtures/store".into(),
        mode: StoreLockMode::ReadOnly,
    };
    let mutable = StoreError::StoreLocked {
        path: "fixtures/store".into(),
        mode: StoreLockMode::Mutable,
    };

    assert_display_contains(&read_only, "read-only");
    assert_display_contains(&mutable, "mutable");
}