generated from eblume/project-template
97 lines
2.9 KiB
Rust
97 lines
2.9 KiB
Rust
|
|
//! CLI tests (tech-spec §9): run the real `heph` binary against a real `hephd`
|
||
|
|
//! over a unix socket, and assert output + side effects (export files).
|
||
|
|
|
||
|
|
use std::path::PathBuf;
|
||
|
|
use std::process::Command;
|
||
|
|
use std::thread;
|
||
|
|
use std::time::Duration;
|
||
|
|
|
||
|
|
use tokio::net::UnixListener;
|
||
|
|
|
||
|
|
use heph_core::{FixedClock, LocalStore};
|
||
|
|
use hephd::Daemon;
|
||
|
|
|
||
|
|
const NOW: i64 = 1_704_067_200_000;
|
||
|
|
|
||
|
|
/// Spawn a daemon on its own thread+runtime; return (socket, tempdir).
|
||
|
|
fn spawn_daemon() -> (PathBuf, tempfile::TempDir) {
|
||
|
|
let dir = tempfile::tempdir().unwrap();
|
||
|
|
let db = dir.path().join("heph.db");
|
||
|
|
let socket = dir.path().join("d.sock");
|
||
|
|
|
||
|
|
let store = LocalStore::open(&db, Box::new(FixedClock(NOW))).unwrap();
|
||
|
|
let socket_for_thread = socket.clone();
|
||
|
|
thread::spawn(move || {
|
||
|
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||
|
|
.enable_all()
|
||
|
|
.build()
|
||
|
|
.unwrap();
|
||
|
|
rt.block_on(async move {
|
||
|
|
let listener = UnixListener::bind(&socket_for_thread).unwrap();
|
||
|
|
let _ = Daemon::new(store).serve(listener).await;
|
||
|
|
});
|
||
|
|
});
|
||
|
|
for _ in 0..200 {
|
||
|
|
if socket.exists() {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
thread::sleep(Duration::from_millis(5));
|
||
|
|
}
|
||
|
|
(socket, dir)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Run the `heph` binary against `socket`; return (stdout, success).
|
||
|
|
fn heph(socket: &std::path::Path, args: &[&str]) -> (String, bool) {
|
||
|
|
let out = Command::new(env!("CARGO_BIN_EXE_heph"))
|
||
|
|
.arg("--socket")
|
||
|
|
.arg(socket)
|
||
|
|
.args(args)
|
||
|
|
.output()
|
||
|
|
.expect("run heph");
|
||
|
|
(
|
||
|
|
String::from_utf8_lossy(&out.stdout).into_owned(),
|
||
|
|
out.status.success(),
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn task_then_next_shows_the_task() {
|
||
|
|
let (socket, _dir) = spawn_daemon();
|
||
|
|
|
||
|
|
let (out, ok) = heph(&socket, &["task", "Buy milk", "--attention", "red"]);
|
||
|
|
assert!(ok, "task create failed: {out}");
|
||
|
|
assert!(out.contains("Created task"), "{out}");
|
||
|
|
|
||
|
|
let (out, ok) = heph(&socket, &["next"]);
|
||
|
|
assert!(ok);
|
||
|
|
assert!(out.contains("[red]"), "{out}");
|
||
|
|
assert!(out.contains("Buy milk"), "{out}");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn next_on_empty_store_is_friendly() {
|
||
|
|
let (socket, _dir) = spawn_daemon();
|
||
|
|
let (out, ok) = heph(&socket, &["next"]);
|
||
|
|
assert!(ok);
|
||
|
|
assert!(out.contains("Nothing actionable"), "{out}");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn export_writes_markdown_files() {
|
||
|
|
let (socket, dir) = spawn_daemon();
|
||
|
|
heph(&socket, &["doc", "Roof log", "--body", "# Roof"]);
|
||
|
|
|
||
|
|
let export_dir = dir.path().join("export");
|
||
|
|
let (out, ok) = heph(&socket, &["export", export_dir.to_str().unwrap()]);
|
||
|
|
assert!(ok, "export failed: {out}");
|
||
|
|
assert!(out.contains("Exported 1 nodes"), "{out}");
|
||
|
|
|
||
|
|
// The doc landed as a .md file under doc/.
|
||
|
|
let docs: Vec<_> = std::fs::read_dir(export_dir.join("doc"))
|
||
|
|
.unwrap()
|
||
|
|
.filter_map(|e| e.ok())
|
||
|
|
.collect();
|
||
|
|
assert_eq!(docs.len(), 1);
|
||
|
|
let text = std::fs::read_to_string(docs[0].path()).unwrap();
|
||
|
|
assert!(text.contains("title: \"Roof log\""), "{text}");
|
||
|
|
}
|