use std::path::{Path, PathBuf};
fn fixture(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("quarb-trees-{}-{tag}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(dir.join("site")).unwrap();
std::fs::create_dir_all(dir.join("docs/guide")).unwrap();
let w = |p: &str, text: &str| std::fs::write(dir.join(p), text).unwrap();
w(
"main.json",
r##"{"home": {"$ref": "other.json#/shared/x"},
"far": {"$ref": "https://example.org/schema.json#/definitions/address"},
"page": {"$ref": "https://docs.example.org/guide/intro.html"},
"book": {"$ref": "urn:isbn:0141439513"},
"row": {"$ref": "data.csv!/row[2]"},
"lost": {"$ref": "nowhere.json#/x"}}"##,
);
w("other.json", r#"{"shared": {"x": {"v": 42}}}"#);
w(
"schema.json",
r#"{"definitions": {"address": {"type": "object"}}}"#,
);
w(
"catalogue.json",
r#"{"0141439513": {"title": "Pride and Prejudice"}}"#,
);
w("data.csv", "id,name\n1,ada\n2,bo\n");
w(
"site/a.html",
r#"<html><body><h1 id="top">A</h1><a href="b.html#sec">to b</a></body></html>"#,
);
w(
"site/b.html",
r#"<html><body><h2 id="sec">B section</h2><a href="a.html">back</a></body></html>"#,
);
w(
"docs/guide/intro.html",
"<html><body><h1>Intro</h1></body></html>",
);
w(
"trees.model",
"mount main: main.json;\n\
mount other: other.json;\n\
mount schema: schema.json;\n\
mount catalogue: catalogue.json;\n\
mount data: data.csv;\n\
mount docs: docs/;\n\
tree https://example.org/schema.json: /schema;\n\
tree https://docs.example.org/: /docs;\n\
tree urn:isbn:: /catalogue;\n",
);
dir
}
fn qua(dir: &Path, args: &[&str]) -> (String, String) {
let out = std::process::Command::new(env!("CARGO_BIN_EXE_qua"))
.env("QUARB_LOCALE", "C")
.current_dir(dir)
.args(args)
.stdin(std::process::Stdio::null())
.output()
.unwrap();
(
String::from_utf8_lossy(&out.stdout).trim().to_string(),
String::from_utf8_lossy(&out.stderr).trim().to_string(),
)
}
#[test]
fn a_mounts_own_target_is_its_identity() {
let dir = fixture("auto");
let (out, _) = qua(
&dir,
&["/main/home-->::v", "main=main.json", "other=other.json"],
);
assert_eq!(out, "42");
let (out, _) = qua(&dir, &["--graft", "//a-->::", "site"]);
assert!(out.starts_with("B section"), "{out}");
let (out, _) = qua(&dir, &["--graft", "/site//a-->::", "site=site"]);
assert!(out.starts_with("B section"), "{out}");
let _ = std::fs::remove_dir_all(dir);
}
#[test]
fn declared_trees_exact_base_urn_and_locator() {
let dir = fixture("model");
let run = |q: &str| qua(&dir, &["--graft", "--model", "trees.model", q]).0;
assert_eq!(run("/main/far-->::type"), "object");
assert_eq!(run("/main/page-->//h1::"), "Intro");
assert_eq!(run("/main/book-->::title"), "Pride and Prejudice");
assert_eq!(run("/main/row-->::name"), "bo");
let (out, err) = qua(
&dir,
&["--explain", "--model", "trees.model", "/main/lost-->"],
);
assert_eq!(out, "");
assert!(
err.contains("unresolved external reference") && err.contains("nowhere.json"),
"{err}"
);
let _ = std::fs::remove_dir_all(dir);
}
#[test]
fn a_tree_is_refused_where_its_path_names_no_node_or_is_declared_twice() {
let dir = fixture("refuse");
std::fs::write(
dir.join("bad.model"),
"mount main: main.json;\ntree https://example.org/x: /nowhere;\n",
)
.unwrap();
let (_, err) = qua(&dir, &["--model", "bad.model", "/main"]);
assert!(err.contains("names no node"), "{err}");
std::fs::write(
dir.join("twice.model"),
"mount main: main.json;\ntree https://example.org/x: /main;\ntree https://example.org/x: /main;\n",
)
.unwrap();
let (_, err) = qua(&dir, &["--model", "twice.model", "/main"]);
assert!(err.contains("declared twice"), "{err}");
let _ = std::fs::remove_dir_all(dir);
}