7a70adc7ac
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
130 lines
4.7 KiB
Rust
130 lines
4.7 KiB
Rust
//! Shared helpers for the integration tests: temp vaults and the real binary.
|
|
|
|
#![allow(dead_code)] // every helper is used by one of the two test binaries
|
|
|
|
use std::io::Write;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{Command, Output, Stdio};
|
|
|
|
use serde_json::Value;
|
|
|
|
/// What one CLI run produced.
|
|
pub struct Run {
|
|
/// The exit code: 0 ok, 1 command error, 2 usage error, 3 vault.
|
|
pub code: i32,
|
|
/// Standard output, as text.
|
|
pub stdout: String,
|
|
/// Standard error, as text.
|
|
pub stderr: String,
|
|
}
|
|
|
|
impl Run {
|
|
/// The `result` of the JSON envelope.
|
|
pub fn result(&self) -> Value {
|
|
let envelope = self.envelope();
|
|
envelope.get("result").cloned().unwrap_or(Value::Null)
|
|
}
|
|
|
|
/// The whole JSON envelope.
|
|
pub fn envelope(&self) -> Value {
|
|
serde_json::from_str(&self.stdout).unwrap_or_else(|error| panic!("stdout is not JSON ({error}): {}", self.stdout))
|
|
}
|
|
|
|
/// The `error.code` of the JSON envelope.
|
|
pub fn error_code(&self) -> String {
|
|
self.envelope().get("error").and_then(|error| error.get("code")).and_then(Value::as_str).unwrap_or_default().to_owned()
|
|
}
|
|
}
|
|
|
|
/// An empty vault under the system temp directory, unique per test process.
|
|
pub fn empty_vault(name: &str) -> PathBuf {
|
|
let root = std::env::temp_dir().join(format!("notescraft-cli-{}-{name}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&root);
|
|
std::fs::create_dir_all(&root).expect("create the temp vault");
|
|
root
|
|
}
|
|
|
|
/// A vault with notes, links, tags, a duplicate name, a broken frontmatter
|
|
/// block, an attachment and a template.
|
|
pub fn sample_vault(name: &str) -> PathBuf {
|
|
let root = empty_vault(name);
|
|
note(&root, "Plan.md", "---\ntags: [work, work/q1]\n---\n# Plan\n\nSee [[Missing]] and [[Notes/Other]].\n");
|
|
note(&root, "Notes/Other.md", "Body mentioning Plan in plain text.\n\n![[photo.png]]\n");
|
|
note(&root, "Notes/plan.md", "A second note with the same name.\n");
|
|
note(&root, "Notes/Broken.md", "---\nbroken: true\nthe block is never closed\n");
|
|
note(&root, "Templates/Daily.md", "# {{title}}\n\n{{date}}\n");
|
|
write_file(&root, "photo.png", "not really a png");
|
|
root
|
|
}
|
|
|
|
/// Writes a note, creating its folders.
|
|
pub fn note(root: &Path, path: &str, content: &str) {
|
|
write_file(root, path, content);
|
|
}
|
|
|
|
/// Writes any file, creating its folders.
|
|
pub fn write_file(root: &Path, path: &str, content: &str) {
|
|
let full = root.join(path);
|
|
if let Some(parent) = full.parent() {
|
|
std::fs::create_dir_all(parent).expect("create the folder");
|
|
}
|
|
std::fs::write(&full, content).expect("write the file");
|
|
}
|
|
|
|
/// The bytes of a vault file.
|
|
pub fn read_file(root: &Path, path: &str) -> String {
|
|
std::fs::read_to_string(root.join(path)).expect("read the file")
|
|
}
|
|
|
|
/// Whether a vault file exists.
|
|
pub fn exists(root: &Path, path: &str) -> bool {
|
|
root.join(path).exists()
|
|
}
|
|
|
|
/// Runs the binary with `args` and no stdin, with `vault` in the environment.
|
|
pub fn run(vault: &Path, args: &[&str]) -> Run {
|
|
spawn(vault, args, None)
|
|
}
|
|
|
|
/// Runs the binary with `args` and feeds `stdin` to it.
|
|
pub fn run_with_stdin(vault: &Path, args: &[&str], stdin: &str) -> Run {
|
|
spawn(vault, args, Some(stdin))
|
|
}
|
|
|
|
/// Runs the binary with no vault at all.
|
|
pub fn run_without_vault(args: &[&str]) -> Run {
|
|
let output = Command::new(env!("CARGO_BIN_EXE_notescraft-cli")).env_remove("NOTESCRAFT_VAULT").args(args).output().expect("run the binary");
|
|
done(&output)
|
|
}
|
|
|
|
/// Runs the binary with raw (possibly non-UTF-8) arguments and no vault.
|
|
pub fn run_raw(args: &[std::ffi::OsString]) -> Run {
|
|
let output = Command::new(env!("CARGO_BIN_EXE_notescraft-cli")).env_remove("NOTESCRAFT_VAULT").args(args).output().expect("run the binary");
|
|
done(&output)
|
|
}
|
|
|
|
/// One process: `NOTESCRAFT_VAULT` is set, `stdin` is optional.
|
|
fn spawn(vault: &Path, args: &[&str], stdin: Option<&str>) -> Run {
|
|
let mut child = Command::new(env!("CARGO_BIN_EXE_notescraft-cli"))
|
|
.env("NOTESCRAFT_VAULT", vault)
|
|
.args(args)
|
|
.stdin(if stdin.is_some() { Stdio::piped() } else { Stdio::null() })
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.spawn()
|
|
.expect("spawn the binary");
|
|
if let Some(text) = stdin {
|
|
child.stdin.as_mut().expect("stdin is piped").write_all(text.as_bytes()).expect("write stdin");
|
|
}
|
|
done(&child.wait_with_output().expect("wait for the binary"))
|
|
}
|
|
|
|
/// The exit code and the two streams as text.
|
|
fn done(output: &Output) -> Run {
|
|
Run {
|
|
code: output.status.code().unwrap_or(-1),
|
|
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
|
|
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
|
|
}
|
|
}
|