hephaestus/crates/heph-core/tests/export.rs
Erich Blume 739214bd07
Some checks failed
Build / validate (pull_request) Failing after 2s
heph CLI + export
Slice 7 (tech-spec §1, §5, §9).

- Export (heph-core): render each non-tombstoned node to `<kind>/<id>.md`
  with YAML frontmatter (id, kind, title, timestamps, task scalars,
  aliases, outgoing links) + body. One-way snapshot; `Store::export`
  writes the tree; tombstones excluded. Added `export` RPC method and
  Error::Io.
- `heph` CLI (clap): thin client of hephd over the socket — `next`
  (concise ranked rows), `task`, `doc`, `get`, `export`. Never touches
  SQLite directly.

Tests: 3 export render unit + 2 export round-trip integration + 3 CLI
process tests driving the real `heph` binary against a real daemon
(task→next, empty-store message, export writes files). 70 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:33:59 -07:00

58 lines
2.1 KiB
Rust

//! `Store::export` writes a faithful .md tree (tech-spec §5, slice 7).
use heph_core::{FixedClock, LocalStore, NewNode, NewTask, Store};
fn store() -> LocalStore {
LocalStore::open_in_memory(Box::new(FixedClock(1_700_000_000_000))).unwrap()
}
#[test]
fn export_writes_a_file_per_node_with_frontmatter_and_body() {
let dir = tempfile::tempdir().unwrap();
let mut s = store();
let doc = s
.create_node(NewNode::doc("Roof log", "# Roof\n\nCalled contractor."))
.unwrap();
let task = s
.create_task(NewTask {
title: "Fix roof".into(),
..Default::default()
})
.unwrap();
// task.create also makes a canonical context doc → 3 nodes total.
let count = s.export(dir.path()).unwrap();
assert_eq!(count, 3);
// The doc file exists with frontmatter and its body.
let doc_file = dir.path().join(format!("doc/{}.md", doc.id));
let doc_text = std::fs::read_to_string(&doc_file).unwrap();
assert!(doc_text.starts_with("---\n"));
assert!(doc_text.contains(&format!("id: {}\n", doc.id)));
assert!(doc_text.contains("kind: doc\n"));
assert!(doc_text.contains("title: \"Roof log\"\n"));
assert!(doc_text.ends_with("# Roof\n\nCalled contractor.\n"));
// The task file carries its scalars and the canonical-context link.
let task_file = dir.path().join(format!("task/{}.md", task.node_id));
let task_text = std::fs::read_to_string(&task_file).unwrap();
assert!(task_text.contains("kind: task\n"));
assert!(task_text.contains("state: outstanding\n"));
assert!(task_text.contains("type: canonical-context"));
}
#[test]
fn export_excludes_tombstoned_nodes() {
let dir = tempfile::tempdir().unwrap();
let mut s = store();
let keep = s.create_node(NewNode::doc("Keep", "kept")).unwrap();
let gone = s.create_node(NewNode::doc("Gone", "gone")).unwrap();
s.tombstone_node(&gone.id).unwrap();
let count = s.export(dir.path()).unwrap();
assert_eq!(count, 1);
assert!(dir.path().join(format!("doc/{}.md", keep.id)).exists());
assert!(!dir.path().join(format!("doc/{}.md", gone.id)).exists());
}