aedb 0.3.1

Embedded Rust storage engine with transactional commits, WAL durability, and snapshot-consistent reads
Documentation
use super::{
    SEGMENT_HEADER_SIZE, SEGMENT_MAGIC, SegmentConfig, SegmentError, SegmentHeader, SegmentManager,
};
use crate::wal::frame::FrameReader;
use crate::wal::rotation::RotationReason;
use std::fs;
use std::io::Cursor;
use std::time::{Duration, Instant};
use tempfile::tempdir;

#[test]
fn segment_header_roundtrip_and_magic_validation() {
    let header = SegmentHeader::new(42, 7, [9u8; 32]);
    let bytes = header.to_bytes();
    assert_eq!(bytes.len(), SEGMENT_HEADER_SIZE);
    let decoded = SegmentHeader::from_bytes(&bytes).expect("decode");
    assert_eq!(decoded.magic, SEGMENT_MAGIC);
    assert_eq!(decoded.instance_id, 42);
    assert_eq!(decoded.segment_seq, 7);
    assert_eq!(decoded.prev_segment_hash, [9u8; 32]);

    let mut bad = bytes;
    bad[0] = 0;
    assert!(matches!(
        SegmentHeader::from_bytes(&bad),
        Err(SegmentError::InvalidMagic)
    ));
}

#[test]
fn size_rotation_trigger_and_no_split_invariant() {
    let dir = tempdir().expect("temp dir");
    let mut manager = SegmentManager::new(
        dir.path(),
        SegmentConfig {
            max_segment_bytes: 160,
            max_segment_age: Duration::from_secs(3600),
        },
        1,
    );
    manager.open_active(1).expect("open");

    manager
        .append_frame(1, 1, 0x01, &[0u8; 128])
        .expect("append");

    assert_eq!(manager.should_rotate(), Some(RotationReason::Size));
    let closed = manager.rotate().expect("rotate");
    assert_eq!(closed.segment_seq, 1);
    assert!(closed.size_bytes > 160);
}

#[test]
fn time_rotation_trigger() {
    let dir = tempdir().expect("temp dir");
    let mut manager = SegmentManager::new(
        dir.path(),
        SegmentConfig {
            max_segment_bytes: 10_000,
            max_segment_age: Duration::from_millis(10),
        },
        2,
    );
    manager.open_active(1).expect("open");
    manager.append_frame(1, 1, 0x01, &[1]).expect("append");

    let now = Instant::now() + Duration::from_millis(20);
    assert_eq!(manager.should_rotate_at(now), Some(RotationReason::Time));
}

#[test]
fn hash_chain_propagates_across_rotations() {
    let dir = tempdir().expect("temp dir");
    let mut manager = SegmentManager::new(
        dir.path(),
        SegmentConfig {
            max_segment_bytes: 200,
            max_segment_age: Duration::from_secs(3600),
        },
        3,
    );
    manager.open_active(1).expect("open");

    let mut closed = Vec::new();
    for i in 1..=5 {
        manager
            .append_frame(i, i, 0x01, &[i as u8; 128])
            .expect("append");
        if manager.should_rotate().is_some() {
            closed.push(manager.rotate().expect("rotate"));
        }
    }
    closed.push(manager.close_active().expect("close"));
    assert!(closed.len() >= 2);

    for s in &closed {
        let data = fs::read(&s.path).expect("read segment");
        let header: [u8; SEGMENT_HEADER_SIZE] =
            data[..SEGMENT_HEADER_SIZE].try_into().expect("header");
        let parsed = SegmentHeader::from_bytes(&header).expect("header parse");
        if parsed.segment_seq == 1 {
            assert_eq!(parsed.prev_segment_hash, [0u8; 32]);
        } else {
            let prev = closed
                .iter()
                .find(|x| x.segment_seq + 1 == parsed.segment_seq)
                .expect("previous segment");
            assert_eq!(parsed.prev_segment_hash, prev.hash);
        }
    }
}

#[test]
fn close_active_hash_matches_segment_bytes() {
    let dir = tempdir().expect("temp dir");
    let mut manager = SegmentManager::new(
        dir.path(),
        SegmentConfig {
            max_segment_bytes: 10_000,
            max_segment_age: Duration::from_secs(3600),
        },
        11,
    );
    manager.open_active(1).expect("open");
    manager
        .append_frame(1, 1, 0x01, b"segment-payload")
        .expect("append");

    let closed = manager.close_active().expect("close");
    let bytes = fs::read(&closed.path).expect("read segment");

    assert_eq!(closed.hash, *blake3::hash(&bytes).as_bytes());
}

#[test]
fn append_frames_batch_writes_multiple_frames_in_order() {
    let dir = tempdir().expect("temp dir");
    let mut manager = SegmentManager::new(
        dir.path(),
        SegmentConfig {
            max_segment_bytes: 10_000,
            max_segment_age: Duration::from_secs(3600),
        },
        9,
    );
    manager.open_active(1).expect("open");

    let batch = [
        super::PendingFrame {
            seq: 11,
            timestamp_micros: 111,
            payload_type: 0x01,
            payload: b"first",
        },
        super::PendingFrame {
            seq: 12,
            timestamp_micros: 222,
            payload_type: 0x02,
            payload: b"second",
        },
    ];
    manager
        .append_frames_with_sync(&batch, false)
        .expect("append batch");
    let closed = manager.close_active().expect("close");

    let data = fs::read(&closed.path).expect("read segment");
    let mut reader = FrameReader::new(Cursor::new(&data[SEGMENT_HEADER_SIZE..]));
    let first = reader
        .next_frame()
        .expect("first frame parse")
        .expect("first frame");
    assert_eq!(first.commit_seq, 11);
    assert_eq!(first.timestamp_micros, 111);
    assert_eq!(first.payload_type, 0x01);
    assert_eq!(first.payload, b"first");

    let second = reader
        .next_frame()
        .expect("second frame parse")
        .expect("second frame");
    assert_eq!(second.commit_seq, 12);
    assert_eq!(second.timestamp_micros, 222);
    assert_eq!(second.payload_type, 0x02);
    assert_eq!(second.payload, b"second");
    assert!(reader.next_frame().expect("end parse").is_none());
}