Files

244 lines
7.1 KiB
Plaintext
Raw Permalink Normal View History

use mem_store::{EventRecord, ObsidianProjector, PgRepo, MemoryNode, VectorKind, Level, RebuildState};
use serde_json::json;
use std::fs;
#[test]
fn m2_gate_vault_byte_identical_rebuild() {
let _ = fs::remove_dir_all("test_m2_gate_vault1");
let _ = fs::remove_dir_all("test_m2_gate_vault2");
// Create sample events
let events = vec![
EventRecord {
project: "test".to_string(),
query: "q1".to_string(),
run: "run1".to_string(),
turn: 1,
event_type: "Gate".to_string(),
data: json!({}),
},
EventRecord {
project: "test".to_string(),
query: "q1".to_string(),
run: "run1".to_string(),
turn: 2,
event_type: "Evidence".to_string(),
data: json!({"parent": "source-001"}),
},
EventRecord {
project: "test".to_string(),
query: "q2".to_string(),
run: "run1".to_string(),
turn: 1,
event_type: "Gate".to_string(),
data: json!({}),
},
];
// First rebuild
let proj1 = ObsidianProjector::new("log", "test_m2_gate_vault1", false);
proj1.project(&events).unwrap();
// Second rebuild (should be identical)
let proj2 = ObsidianProjector::new("log", "test_m2_gate_vault2", false);
proj2.project(&events).unwrap();
// Compare all files byte-by-byte
let files1 = collect_md_files("test_m2_gate_vault1");
let files2 = collect_md_files("test_m2_gate_vault2");
assert_eq!(
files1.len(),
files2.len(),
"Rebuild produced different number of files"
);
for file in files1.iter() {
let c1 = fs::read_to_string(file).unwrap();
let c2 = fs::read_to_string(file.replace("test_m2_gate_vault1", "test_m2_gate_vault2")).unwrap();
assert_eq!(
c1, c2,
"File {} is not byte-identical after rebuild",
file
);
}
let _ = fs::remove_dir_all("test_m2_gate_vault1");
let _ = fs::remove_dir_all("test_m2_gate_vault2");
}
#[test]
fn m2_gate_pg_repo_idempotent_upsert() {
// Proof: rebuilding repository produces identical state
let mut repo1 = PgRepo::new();
let mut repo2 = PgRepo::new();
let nodes = vec![
MemoryNode {
sha256: "abc1".to_string(),
level: Level::L0,
project: "p1".to_string(),
text: "text1".to_string(),
tokens: 100,
},
MemoryNode {
sha256: "abc2".to_string(),
level: Level::L1,
project: "p1".to_string(),
text: "text2".to_string(),
tokens: 200,
},
];
// Upsert into repo1
repo1.upsert_many(&nodes).unwrap();
repo1.insert_edges("abc2", &["abc1".to_string()]).unwrap();
// Add vector
repo1.upsert_vector("abc1", VectorKind::Text, &[1.0, 0.0, 0.0]).unwrap();
repo1.upsert_vector("abc2", VectorKind::Text, &[1.0, 0.0, 0.0]).unwrap();
// Rebuild: upsert same nodes into repo2
repo2.upsert_many(&nodes).unwrap();
repo2.insert_edges("abc2", &["abc1".to_string()]).unwrap();
repo2.upsert_vector("abc1", VectorKind::Text, &[1.0, 0.0, 0.0]).unwrap();
repo2.upsert_vector("abc2", VectorKind::Text, &[1.0, 0.0, 0.0]).unwrap();
// Verify identical state
assert_eq!(repo1.node_count(), repo2.node_count());
assert_eq!(repo1.edge_count(), repo2.edge_count());
let p1_nodes = repo1.all_nodes();
let p2_nodes = repo2.all_nodes();
assert_eq!(p1_nodes.len(), p2_nodes.len());
for (n1, n2) in p1_nodes.iter().zip(p2_nodes.iter()) {
assert_eq!(n1.sha256, n2.sha256);
assert_eq!(n1.level, n2.level);
assert_eq!(n1.project, n2.project);
assert_eq!(n1.text, n2.text);
}
}
#[test]
fn m2_gate_rebuild_state_consistency() {
// Proof: rebuild from events produces consistent state
let events = vec![
EventRecord {
project: "p".to_string(),
query: "q".to_string(),
run: "r1".to_string(),
turn: 1,
event_type: "Gate".to_string(),
data: json!({}),
},
EventRecord {
project: "p".to_string(),
query: "q".to_string(),
run: "r1".to_string(),
turn: 2,
event_type: "Evidence".to_string(),
data: json!({}),
},
];
// Rebuild state twice
let state1 = RebuildState::from_events(&events).unwrap();
let state2 = RebuildState::from_events(&events).unwrap();
// Verify identical
assert_eq!(state1.event_count, state2.event_count);
assert_eq!(state1.chunks_seen, state2.chunks_seen);
assert_eq!(state1.chunks_used, state2.chunks_used);
}
#[test]
fn m2_gate_no_hidden_state() {
// Proof: rebuild with no prior state produces same result
let events = vec![
EventRecord {
project: "fresh".to_string(),
query: "newq".to_string(),
run: "run1".to_string(),
turn: 1,
event_type: "Gate".to_string(),
data: json!({}),
},
];
// Rebuild 1: fresh repo
let mut repo1 = PgRepo::new();
let node1 = MemoryNode {
sha256: "new1".to_string(),
level: Level::L0,
project: "fresh".to_string(),
text: "new memory".to_string(),
tokens: 50,
};
repo1.upsert_node(&node1).unwrap();
// Rebuild 2: same
let mut repo2 = PgRepo::new();
let node2 = MemoryNode {
sha256: "new1".to_string(),
level: Level::L0,
project: "fresh".to_string(),
text: "new memory".to_string(),
tokens: 50,
};
repo2.upsert_node(&node2).unwrap();
assert_eq!(repo1.node_count(), repo2.node_count());
}
#[test]
fn m2_gate_clear_project_is_safe() {
// Proof: clearing one project doesn't affect others
let mut repo = PgRepo::new();
let n1 = MemoryNode {
sha256: "n1".to_string(),
level: Level::L1,
project: "keep".to_string(),
text: "keep".to_string(),
tokens: 100,
};
let n2 = MemoryNode {
sha256: "n2".to_string(),
level: Level::L1,
project: "delete".to_string(),
text: "delete".to_string(),
tokens: 100,
};
repo.upsert_node(&n1).unwrap();
repo.upsert_node(&n2).unwrap();
assert_eq!(repo.node_count(), 2);
// Clear one project
repo.clear_project("delete").unwrap();
assert_eq!(repo.node_count(), 1);
assert_eq!(repo.all_nodes()[0].project, "keep");
}
/// Collect all .md files in directory recursively.
fn collect_md_files(dir: &str) -> Vec<String> {
let mut files = Vec::new();
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() && path.extension().map_or(false, |e| e == "md") {
files.push(path.to_string_lossy().to_string());
} else if path.is_dir() {
let subfiles = collect_md_files(&path.to_string_lossy());
files.extend(subfiles);
}
}
}
files.sort();
files
}