use std::sync::Arc;
use grit_core::{Budget, Grit, ManualClock, Options, Query, Traversal};
use uuid::Uuid;
fn open_fixture_copy(dir: &tempfile::TempDir, name: &str) -> Grit {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures")
.join(name);
let dst = dir.path().join(name);
std::fs::copy(&src, &dst).unwrap();
Grit::open(
&dst,
Options::new("migration-test").clock(Arc::new(ManualClock::new(2_000_000))),
)
.unwrap()
}
#[test]
fn v1_fixture_opens_and_reads() {
let dir = tempfile::tempdir().unwrap();
let g = open_fixture_copy(&dir, "v1.db");
let stats = g.stats().unwrap();
assert_eq!(
(
stats.nodes,
stats.edges,
stats.episodes,
stats.mentions,
stats.oplog,
stats.purged
),
(5, 2, 1, 2, 12, 1)
);
let hits = g
.search(Query::text("fixtures").budget(Budget::items(5)))
.unwrap();
assert!(!hits.is_empty(), "episode content must be FTS-searchable");
let n0 = Uuid::from_u128(1);
let sub = g.traverse(&[n0], &Traversal::default().depth(1)).unwrap();
assert_eq!(sub.edges.len(), 1, "n0 -R-> n1 must survive");
assert_eq!(g.mentions_of(n0).unwrap().len(), 1);
let merged = g.node(Uuid::from_u128(6)).unwrap().unwrap();
assert_eq!(merged.merged_into, Some(Uuid::from_u128(5)));
assert!(
g.node(Uuid::from_u128(4)).unwrap().is_none(),
"purged node stays gone"
);
let e2 = Uuid::from_u128(0x101);
let edge = g.edge(e2).unwrap().unwrap();
assert_eq!(edge.invalid_at, Some(1_000_500));
assert_eq!(grit_core::SCHEMA_VERSION, 3);
g.apply(grit_core::GraphOp::AddNode {
id: g.new_id(),
kind: "k".into(),
name: "post-migration write".into(),
summary: String::new(),
attrs: serde_json::json!({}),
group_id: String::new(),
})
.unwrap();
assert_eq!(g.stats().unwrap().nodes, 6);
g.apply(grit_core::GraphOp::UpdateNode {
id: n0,
name: None,
summary: Some("post-migration summary".into()),
kind: None,
attrs: None,
})
.unwrap();
assert_eq!(
g.node(n0).unwrap().unwrap().summary,
"post-migration summary"
);
}
fn build_v2_content(g: &Grit) {
use grit_core::GraphOp;
let n = |i: u128| Uuid::from_u128(i);
let e = |i: u128| Uuid::from_u128(0x100 + i);
let ep = |i: u128| Uuid::from_u128(0x200 + i);
for i in 1..=4u128 {
g.apply(GraphOp::AddNode {
id: n(i),
kind: "k".into(),
name: format!("node-{i}"),
summary: String::new(),
attrs: serde_json::json!({"i": i}),
group_id: "g".into(),
})
.unwrap();
}
g.apply(GraphOp::AddEdge {
id: e(1),
src: n(1),
dst: n(2),
rel: "R".into(),
fact: "node-1 relates to node-2".into(),
attrs: serde_json::json!({}),
group_id: "g".into(),
valid_at: Some(1_000_000),
invalid_at: None,
})
.unwrap();
g.apply(GraphOp::AddEpisode {
id: ep(1),
source: "fixtures".into(),
kind: String::new(),
content: "episode exercising the v2 fixture tables".into(),
occurred_at: 1_000_100,
group_id: "g".into(),
mentions: vec![n(1), e(1)],
})
.unwrap();
g.apply(GraphOp::InvalidateEdge {
edge_id: e(1),
invalid_at: 1_000_500,
})
.unwrap();
g.apply(GraphOp::UpdateNode {
id: n(1),
name: Some("node-1 promoted".into()),
summary: Some("updated summary".into()),
kind: None,
attrs: None,
})
.unwrap();
g.apply(GraphOp::UpdateNode {
id: n(9),
name: None,
summary: Some("pending until node-9 lands".into()),
kind: None,
attrs: None,
})
.unwrap();
g.apply(GraphOp::MergeNodes {
from: n(3),
into: n(2),
})
.unwrap();
g.apply(GraphOp::Purge { ids: vec![n(4)] }).unwrap();
g.register_embedding_model("fixture-model", 4, "1").unwrap();
}
fn assert_v2_content(g: &Grit) {
let n1 = Uuid::from_u128(1);
let node = g.node(n1).unwrap().unwrap();
assert_eq!(node.name, "node-1 promoted");
assert_eq!(node.summary, "updated summary");
assert_eq!(node.kind, "k", "untouched field keeps AddNode base");
let merged = g.node(Uuid::from_u128(3)).unwrap().unwrap();
assert_eq!(merged.merged_into, Some(Uuid::from_u128(2)));
assert!(g.node(Uuid::from_u128(4)).unwrap().is_none());
assert_eq!(
g.edge(Uuid::from_u128(0x101)).unwrap().unwrap().invalid_at,
Some(1_000_500)
);
let hits = g
.search(Query::text("promoted").budget(Budget::items(5)))
.unwrap();
assert!(!hits.is_empty(), "updated node name must be FTS-searchable");
}
#[test]
fn v2_fixture_opens_and_reads() {
let dir = tempfile::tempdir().unwrap();
let g = open_fixture_copy(&dir, "v2.db");
assert_v2_content(&g);
let eps = g.episodes_in_group("g").unwrap();
assert_eq!(eps.len(), 1);
assert_eq!(eps[0].kind, "", "pre-v3 episode gets the '' default");
let n9 = Uuid::from_u128(9);
g.apply(grit_core::GraphOp::AddNode {
id: n9,
kind: "k".into(),
name: "node-9".into(),
summary: String::new(),
attrs: serde_json::json!({}),
group_id: "g".into(),
})
.unwrap();
assert_eq!(
g.node(n9).unwrap().unwrap().summary,
"pending until node-9 lands"
);
}
fn build_v3_content(g: &Grit) {
build_v2_content(g);
g.apply(grit_core::GraphOp::AddEpisode {
id: Uuid::from_u128(0x202),
source: "doc:profile.md".into(),
kind: "text".into(),
content: "a document-chunk episode exercising the v3 kind column".into(),
occurred_at: 1_000_200,
group_id: "g".into(),
mentions: vec![Uuid::from_u128(1)],
})
.unwrap();
}
fn assert_v3_content(g: &Grit) {
assert_v2_content(g);
let eps = g.episodes_in_group("g").unwrap();
assert_eq!(eps.len(), 2);
assert_eq!(eps[0].kind, "", "v2-era episode keeps the '' default");
assert_eq!(eps[1].kind, "text", "v3 kind round-trips");
assert_eq!(eps[1].source, "doc:profile.md");
}
#[test]
fn v3_fixture_opens_and_reads() {
let dir = tempfile::tempdir().unwrap();
let g = open_fixture_copy(&dir, "v3.db");
assert_v3_content(&g);
}
#[test]
#[ignore = "fixture generator — run once per released schema version"]
fn generate_v2_fixture() {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v2.db");
assert!(
!path.exists(),
"fixtures are frozen artifacts — never regenerate {}",
path.display()
);
let dir = tempfile::tempdir().unwrap();
let work = dir.path().join("v2.db");
let g = Grit::open(
&work,
Options::new("fixture-v2").clock(Arc::new(ManualClock::new(1_000_000))),
)
.unwrap();
build_v2_content(&g);
assert_v2_content(&g);
let mut stream = Vec::new();
g.export_jsonl(&mut stream).unwrap();
grit_core::import_jsonl(&path, stream.as_slice()).unwrap();
let check = tempfile::tempdir().unwrap();
let g2 = open_fixture_copy(&check, "v2.db");
assert_v2_content(&g2);
}
#[test]
#[ignore = "fixture generator — run once per released schema version"]
fn generate_v3_fixture() {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v3.db");
assert!(
!path.exists(),
"fixtures are frozen artifacts — never regenerate {}",
path.display()
);
let dir = tempfile::tempdir().unwrap();
let work = dir.path().join("v3.db");
let g = Grit::open(
&work,
Options::new("fixture-v3").clock(Arc::new(ManualClock::new(1_000_000))),
)
.unwrap();
build_v3_content(&g);
assert_v3_content(&g);
let mut stream = Vec::new();
g.export_jsonl(&mut stream).unwrap();
grit_core::import_jsonl(&path, stream.as_slice()).unwrap();
let check = tempfile::tempdir().unwrap();
let g2 = open_fixture_copy(&check, "v3.db");
assert_v3_content(&g2);
}