use std::collections::BTreeMap;
use super::{decode, LegacyBincodeError};
use crate::collective::Collective;
use crate::experience::{Experience, ExperienceType, Severity};
use crate::insight::{DerivedInsight, InsightType};
use crate::relation::{ExperienceRelation, RelationType};
use crate::storage::schema::{ExperienceV2, WatchEventRecord, WatchEventRecordV1};
use crate::types::{CollectiveId, ExperienceId, InstanceId, Timestamp};
pub(crate) const COLLECTIVE_GOLDEN: &[u8] = &[
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x64, 0x65, 0x6d, 0x6f, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x77, 0x6e,
0x65, 0x72, 0x80, 0x01, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
pub(crate) const COLLECTIVE_NO_OWNER_GOLDEN: &[u8] = &[
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x64, 0x65, 0x6d, 0x6f, 0x00, 0x80, 0x01, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
pub(crate) const EXPERIENCE_GOLDEN: &[u8] = &[
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x07, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x75, 0x73, 0x74, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x6f, 0x63, 0x73, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x80,
0x3e, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x07, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x61, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x62, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x61, 0x73, 0x6b, 0x6f, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
pub(crate) const EXPERIENCE_ADVERSARIAL_GOLDEN: &[u8] = &[
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];
pub(crate) const EXPERIENCE_DIFFICULTY_GOLDEN: &[u8] = &[
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x62, 0x6f, 0x6f, 0x6d, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x7f,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];
pub(crate) const EXPERIENCE_SOLUTION_GOLDEN: &[u8] = &[
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
0x33, 0x33, 0x33, 0x33, 0x33, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x69, 0x78,
0x01, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];
pub(crate) const RELATION_GOLDEN: &[u8] = &[
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x03, 0x00, 0x00, 0x00, 0x9a, 0x99, 0x59, 0x3f,
0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x7d, 0xe7, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
];
pub(crate) const INSIGHT_GOLDEN: &[u8] = &[
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51,
0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xbf, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62,
0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x03, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3f, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78,
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
pub(crate) const F32_NAN_GOLDEN: &[u8] = &[0x00, 0x00, 0xc0, 0x7f];
#[test]
fn decode_collective_happy() {
let c: Collective = decode(COLLECTIVE_GOLDEN).expect("collective decodes");
assert_eq!(c.id, CollectiveId::from_bytes([0x11; 16]));
assert_eq!(c.name, "demo");
assert_eq!(c.owner_id.as_deref(), Some("owner"));
assert_eq!(c.embedding_dimension, 384);
assert_eq!(c.created_at, Timestamp::from_millis(1000));
assert_eq!(c.updated_at, Timestamp::from_millis(2000));
}
#[test]
fn decode_collective_none_option() {
let c: Collective = decode(COLLECTIVE_NO_OWNER_GOLDEN).expect("collective decodes");
assert!(c.owner_id.is_none(), "Option None must decode from a 0 tag");
assert_eq!(c.embedding_dimension, 384);
}
#[test]
fn decode_experience_gcounter_multi_entry() {
let e: Experience = decode(EXPERIENCE_GOLDEN).expect("experience decodes");
assert_eq!(e.id, ExperienceId::from_bytes([0x21; 16]));
assert_eq!(e.collective_id, CollectiveId::from_bytes([0x11; 16]));
assert_eq!(e.content, "hello");
assert!(e.embedding.is_empty());
assert!(matches!(e.experience_type, ExperienceType::Fact { .. }));
assert_eq!(e.importance, 0.5);
assert_eq!(e.confidence, 0.25);
let mut expected: BTreeMap<InstanceId, u32> = BTreeMap::new();
expected.insert(InstanceId::from_bytes([0x01; 16]), 3);
expected.insert(InstanceId::from_bytes([0x02; 16]), 7);
assert_eq!(e.applications, expected);
assert_eq!(e.applications(), 10);
assert_eq!(e.domain, vec!["a".to_string(), "bb".to_string()]);
assert!(e.related_files.is_empty());
assert_eq!(e.source_agent.as_str(), "agent");
assert_eq!(e.source_task.as_ref().map(|t| t.as_str()), Some("task"));
assert_eq!(e.timestamp, Timestamp::from_millis(111));
assert_eq!(e.last_reinforced, Timestamp::from_millis(222));
assert!(!e.archived);
}
#[test]
fn decode_experience_adversarial_nan_empty_none() {
let e: Experience = decode(EXPERIENCE_ADVERSARIAL_GOLDEN).expect("adversarial decodes");
assert!(e.importance.is_nan(), "f32 NaN must round-trip");
assert!(e.applications.is_empty());
assert!(e.domain.is_empty());
assert!(e.source_task.is_none());
assert!(matches!(
e.experience_type,
ExperienceType::Generic { category: None }
));
assert_eq!(e.timestamp, Timestamp::from_millis(-1));
assert!(e.archived);
}
#[test]
fn decode_experience_difficulty_nested_enum() {
let e: Experience = decode(EXPERIENCE_DIFFICULTY_GOLDEN).expect("difficulty decodes");
match e.experience_type {
ExperienceType::Difficulty {
description,
severity,
} => {
assert_eq!(description, "boom");
assert_eq!(severity, Severity::Critical);
}
other => panic!("expected Difficulty, got {other:?}"),
}
}
#[test]
fn decode_experience_solution_some_ref_and_bool() {
let e: Experience = decode(EXPERIENCE_SOLUTION_GOLDEN).expect("solution decodes");
match e.experience_type {
ExperienceType::Solution {
problem_ref,
approach,
worked,
} => {
assert_eq!(problem_ref, Some(ExperienceId::from_bytes([0x33; 16])));
assert_eq!(approach, "fix");
assert!(worked);
}
other => panic!("expected Solution, got {other:?}"),
}
}
#[test]
fn decode_relation_enum_u32_tag() {
let r: ExperienceRelation = decode(RELATION_GOLDEN).expect("relation decodes");
assert_eq!(r.relation_type, RelationType::Supersedes);
assert_eq!(r.strength, 0.85);
assert_eq!(r.metadata.as_deref(), Some("{}"));
assert_eq!(r.created_at, Timestamp::from_millis(999));
}
#[test]
fn decode_insight_inline_vecs() {
let i: DerivedInsight = decode(INSIGHT_GOLDEN).expect("insight decodes");
assert_eq!(i.content, "insight");
assert_eq!(i.embedding, vec![0.5f32, -0.5f32]);
assert_eq!(i.source_experience_ids.len(), 2);
assert_eq!(
i.source_experience_ids[0],
ExperienceId::from_bytes([0x61; 16])
);
assert_eq!(i.insight_type, InsightType::Correlation);
assert_eq!(i.confidence, 0.9);
}
#[test]
fn decode_experience_v2_scalar_applications() {
let v2: ExperienceV2 = decode(EXPERIENCE_V2_GOLDEN).expect("ExperienceV2 decodes");
assert_eq!(v2.content, "v2");
assert_eq!(v2.applications, 42u32, "scalar v2 application count");
assert_eq!(v2.importance, 0.5);
assert!(matches!(v2.experience_type, ExperienceType::Generic { .. }));
}
#[test]
fn decode_watch_event_record_v2_has_entity_type() {
let w: WatchEventRecord = decode(WATCH_EVENT_GOLDEN).expect("WatchEventRecord decodes");
assert_eq!(w.entity_id, [0x71; 16]);
assert_eq!(w.collective_id, [0x72; 16]);
assert_eq!(w.timestamp_ms, 12345i64);
}
#[test]
fn decode_watch_event_record_v1_without_entity_type() {
let w: WatchEventRecordV1 = decode(WATCH_EVENT_V1_GOLDEN).expect("WatchEventRecordV1 decodes");
assert_eq!(w.experience_id, [0x71; 16]);
assert_eq!(w.collective_id, [0x72; 16]);
assert_eq!(w.timestamp_ms, 12345i64);
}
#[test]
fn decode_f32_nan_standalone() {
let v: f32 = decode(F32_NAN_GOLDEN).expect("f32 decodes");
assert!(v.is_nan());
}
#[test]
fn decode_rejects_trailing_bytes() {
let mut bytes = COLLECTIVE_GOLDEN.to_vec();
bytes.push(0xFF); let err = decode::<Collective>(&bytes).expect_err("trailing byte must error");
assert_eq!(err, LegacyBincodeError::TrailingBytes);
}
#[test]
fn decode_rejects_truncated_buffer() {
let truncated = &COLLECTIVE_GOLDEN[..COLLECTIVE_GOLDEN.len() - 4];
let err = decode::<Collective>(truncated).expect_err("truncation must error");
assert_eq!(err, LegacyBincodeError::Eof);
}
#[test]
fn decode_rejects_oversized_length_prefix() {
let bytes: &[u8] = &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00];
let err = decode::<String>(bytes).expect_err("oversized length must error");
assert_eq!(err, LegacyBincodeError::LengthOverflow);
}
#[test]
fn decode_rejects_invalid_enum_index() {
let bytes: &[u8] = &[99, 0, 0, 0];
let err = decode::<RelationType>(bytes).expect_err("invalid variant must error");
assert!(matches!(err, LegacyBincodeError::Message(_)));
}
#[test]
fn decode_empty_buffer_errors() {
let err = decode::<Collective>(&[]).expect_err("empty buffer must error");
assert_eq!(err, LegacyBincodeError::Eof);
}
pub(crate) const EXPERIENCE_V2_GOLDEN: &[u8] = &[
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x32, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
pub(crate) const WATCH_EVENT_GOLDEN: &[u8] = &[
0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71,
0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,
0x01, 0x00, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
pub(crate) const WATCH_EVENT_V1_GOLDEN: &[u8] = &[
0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71,
0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,
0x01, 0x00, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];