minerva 0.2.0

Causal ordering for distributed systems
extern crate alloc;

use alloc::vec::Vec;

use crate::metis::{
    Anchor, Locus, Metatheses, MetathesesDecodeBudget, MetathesesDecodeError, Rhapsody,
};

use super::{canonical_record, d, rank, testimony};
use crate::metis::dot::RawDot;

/// The golden layout fixture for version 1 (the cross-repository
/// fixture obligation from the S272 charter): every byte pinned, and the
/// frame round-trips to the record that produced it.
#[test]
fn test_metatheses_to_bytes_layout() {
    let record = canonical_record();
    let frame = record.to_bytes();
    assert_eq!(
        frame,
        [
            0x01, // version (v1)
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // testimony_count = 2
            // row (1, 3): target (1, 1) at the origin, rank physical 7
            0x00, 0x00, 0x00, 0x01, // testimony station = 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // testimony index = 3
            0x00, 0x00, 0x00, 0x01, // target station = 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // target index = 1
            0x00, // anchor tag = origin
            0x01, // rank version
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, // rank physical = 7
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // rank station = 1
            // row (2, 1): target (1, 1) after (1, 3), rank physical 9
            0x00, 0x00, 0x00, 0x02, // testimony station = 2
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // testimony index = 1
            0x00, 0x00, 0x00, 0x01, // target station = 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // target index = 1
            0x01, // anchor tag = after
            0x00, 0x00, 0x00, 0x01, // anchor station = 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // anchor index = 3
            0x01, // rank version
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // rank physical = 9
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // rank station = 1
        ]
    );
    assert_eq!(Metatheses::from_bytes(&frame), Ok(record));
}

/// The empty record is the empty frame: header only, and it round-trips.
#[test]
fn test_metatheses_empty_frame() {
    let record = Metatheses::new();
    let frame = record.to_bytes();
    assert_eq!(
        frame,
        [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
    );
    assert_eq!(Metatheses::from_bytes(&frame), Ok(record));
}

/// A before-anchored testimony carries tag `0x02` and round-trips (the
/// sided-anchor discipline, PRD 0018).
#[test]
fn test_metatheses_before_anchor_round_trips() {
    let mut record = Metatheses::new();
    assert!(record.insert(
        d(1, 1),
        testimony(
            (2, 5),
            Anchor::Before(RawDot {
                station: 1,
                counter: 4
            }),
            11
        )
    ));
    let frame = record.to_bytes();
    assert_eq!(frame[33], 0x02, "the before-anchored row carries tag 0x02");
    let decoded = Metatheses::from_bytes(&frame).expect("a before-anchored frame decodes");
    assert_eq!(decoded, record);
    assert_eq!(decoded.to_bytes(), frame);
}

/// The anchor tag bytes agree with the rhapsody v2 frame's spelling of the
/// same anchors: the two codecs own their spellings independently (R-8),
/// and this pin is what keeps the shared discipline shared.
#[test]
fn test_metatheses_anchor_tags_agree_with_rhapsody() {
    // The rhapsody tag byte sits after its 5-byte header and 12-byte
    // woven dot; the metatheses tag after its 9-byte header and 24-byte
    // dot pair.
    let mut rhapsody = Rhapsody::new();
    assert!(rhapsody.weave(
        d(1, 1),
        Locus {
            anchor: Anchor::Origin,
            rank: rank(7),
        }
    ));
    assert!(rhapsody.weave(
        d(1, 2),
        Locus {
            anchor: Anchor::Before(RawDot {
                station: 1,
                counter: 1
            }),
            rank: rank(9),
        }
    ));
    let rhapsody_frame = rhapsody.to_bytes();

    let mut record = Metatheses::new();
    assert!(record.insert(d(1, 1), testimony((1, 1), Anchor::Origin, 7)));
    assert!(record.insert(
        d(1, 2),
        testimony(
            (1, 1),
            Anchor::Before(RawDot {
                station: 1,
                counter: 1
            }),
            9
        )
    ));
    let metatheses_frame = record.to_bytes();

    // First record: origin tag.
    assert_eq!(rhapsody_frame[17], metatheses_frame[33]);
    // Second record: before tag (offsets past one origin-anchored record).
    assert_eq!(rhapsody_frame[47], metatheses_frame[75]);
    assert_eq!(rhapsody_frame[47], 0x02);
}

/// A zero-index *target* dot is a representable stored value, so the
/// frame carries it: refusing it would break the canonical bijection
/// (whether a target exists is a read, never a wire refusal).
#[test]
fn test_metatheses_zero_target_round_trips() {
    let mut record = Metatheses::new();
    assert!(record.insert(d(1, 1), testimony((3, 0), Anchor::Origin, 7)));
    let frame = record.to_bytes();
    let decoded = Metatheses::from_bytes(&frame).expect("a zero-target frame decodes");
    assert_eq!(decoded, record);
    assert_eq!(decoded.to_bytes(), frame);
}

/// A zero-index *anchor* dot rides the same rule (the rhapsody frame's
/// dangling-anchor precedent: legal value, wire acceptance).
#[test]
fn test_metatheses_zero_anchor_round_trips() {
    let mut record = Metatheses::new();
    assert!(record.insert(
        d(1, 1),
        testimony(
            (2, 2),
            Anchor::After(RawDot {
                station: 3,
                counter: 0
            }),
            7
        )
    ));
    let frame = record.to_bytes();
    let decoded = Metatheses::from_bytes(&frame).expect("a zero-anchor frame decodes");
    assert_eq!(decoded, record);
    assert_eq!(decoded.to_bytes(), frame);
}

/// An unknown version byte refuses before any structural read; there is
/// no fallback decode (v1 is the only version).
#[test]
fn test_metatheses_unknown_version_refused() {
    let mut frame = canonical_record().to_bytes();
    frame[0] = 0x02;
    assert_eq!(
        Metatheses::from_bytes(&frame),
        Err(MetathesesDecodeError::UnknownVersion(0x02))
    );
    assert_eq!(
        Metatheses::from_bytes(&[]),
        Err(MetathesesDecodeError::UnexpectedLength {
            expected: 9,
            found: 0,
        })
    );
}

/// A frame shorter than its declared count refuses in `O(1)` against the
/// per-row minimum, at the header and mid-row alike.
#[test]
fn test_metatheses_truncation_refused() {
    let frame = canonical_record().to_bytes();
    // Cut mid-way through the second row.
    let cut = &frame[..frame.len() - 10];
    assert!(matches!(
        Metatheses::from_bytes(cut),
        Err(MetathesesDecodeError::UnexpectedLength { .. })
    ));
    // A count the buffer cannot back refuses without allocation.
    let mut hostile = Vec::from(&[0x01u8][..]);
    hostile.extend_from_slice(&u64::MAX.to_be_bytes());
    assert!(matches!(
        Metatheses::from_bytes(&hostile),
        Err(MetathesesDecodeError::UnexpectedLength { .. })
    ));
}

/// The impossible-count refusal fires before any row is decoded: a frame
/// declaring one row more than its body backs refuses on the count
/// division alone, even when every carried row is malformed (a corrupted
/// first rank would surface as `Rank` if rows were processed first).
#[test]
fn test_metatheses_impossible_count_refuses_before_rows() {
    let mut frame = canonical_record().to_bytes();
    // Declare a third row the body does not carry.
    frame[1..9].copy_from_slice(&3u64.to_be_bytes());
    // Corrupt the first row's rank version byte: reaching it would
    // refuse as `Rank`, so `UnexpectedLength` proves the preflight ran
    // first.
    frame[34] = 0x7f;
    assert!(matches!(
        Metatheses::from_bytes(&frame),
        Err(MetathesesDecodeError::UnexpectedLength { .. })
    ));
}

/// Trailing bytes past the declared frame refuse: the exact door decodes
/// exactly one frame.
#[test]
fn test_metatheses_trailing_bytes_refused() {
    let mut frame = canonical_record().to_bytes();
    let exact = frame.len();
    frame.push(0x00);
    assert_eq!(
        Metatheses::from_bytes(&frame),
        Err(MetathesesDecodeError::UnexpectedLength {
            expected: exact,
            found: exact + 1,
        })
    );
}

/// A zero-index testimony dot is the non-dot no encoder produces.
#[test]
fn test_metatheses_zero_testimony_dot_refused() {
    let mut frame = canonical_record().to_bytes();
    // Zero the first row's testimony index (bytes 13..21).
    for byte in &mut frame[13..21] {
        *byte = 0;
    }
    assert_eq!(
        Metatheses::from_bytes(&frame),
        Err(MetathesesDecodeError::ZeroDot { station: 1 })
    );
}

/// Duplicate and reordered testimony dots are non-canonical: the record
/// enumerates strictly ascending.
#[test]
fn test_metatheses_non_ascending_refused() {
    let mut record = Metatheses::new();
    assert!(record.insert(d(1, 3), testimony((1, 1), Anchor::Origin, 7)));
    assert!(record.insert(d(1, 4), testimony((1, 1), Anchor::Origin, 9)));
    let frame = record.to_bytes();
    // Both rows are origin-anchored and 42 bytes wide; swapping them
    // reorders, rewriting the second dot to equal the first duplicates.
    let mut swapped = frame.clone();
    swapped[9..51].copy_from_slice(&frame[51..93]);
    swapped[51..93].copy_from_slice(&frame[9..51]);
    assert_eq!(
        Metatheses::from_bytes(&swapped),
        Err(MetathesesDecodeError::NonAscendingTestimonies {
            previous: (1, 4),
            found: (1, 3),
        })
    );
    let mut duplicated = frame;
    duplicated[62] = 0x03;
    assert_eq!(
        Metatheses::from_bytes(&duplicated),
        Err(MetathesesDecodeError::NonAscendingTestimonies {
            previous: (1, 3),
            found: (1, 3),
        })
    );
}

/// An anchor tag above `0x02` refuses.
#[test]
fn test_metatheses_bad_anchor_tag_refused() {
    let mut frame = canonical_record().to_bytes();
    frame[33] = 0x03;
    assert_eq!(
        Metatheses::from_bytes(&frame),
        Err(MetathesesDecodeError::BadAnchorTag { tag: 0x03 })
    );
}

/// A malformed rank frame surfaces as the wrapped stamp error.
#[test]
fn test_metatheses_bad_rank_refused() {
    let mut frame = canonical_record().to_bytes();
    // The first row's rank version byte (offset 34) is not a stamp
    // version.
    frame[34] = 0x7f;
    assert!(matches!(
        Metatheses::from_bytes(&frame),
        Err(MetathesesDecodeError::Rank(_))
    ));
}

/// The budgeted door refuses a declared count past the caller's licence
/// before any row work; an exact budget admits the frame.
#[test]
fn test_metatheses_budget_refusal() {
    let record = canonical_record();
    let frame = record.to_bytes();
    assert_eq!(
        Metatheses::from_bytes_with_budget(&frame, MetathesesDecodeBudget::new(1)),
        Err(MetathesesDecodeError::TooManyTestimonies {
            count: 2,
            budget: 1,
        })
    );
    assert_eq!(
        Metatheses::from_bytes_with_budget(&frame, MetathesesDecodeBudget::new(2)),
        Ok(record)
    );
}