#![forbid(unsafe_code)]
use std::path::{Path, PathBuf};
use filebase::detail::Outcome;
use filebase::query::{Run, Update};
use filebase::Detail;
fn pack(dir: &Path, name: &str, flyleaf: &str) {
std::fs::create_dir_all(dir).unwrap();
let content = dir.join(name);
std::fs::write(&content, format!("the bytes of {name}")).unwrap();
let doc: slpc::toml_edit::DocumentMut = format!(
"slipcase_version = \"{}\"\n{flyleaf}\n[content]\nfile = \"{name}\"\n",
slpc::VERSION
)
.parse()
.unwrap();
let out = std::fs::File::create(dir.join(format!("{name}.slpc"))).unwrap();
slpc::pack_file(&content, doc, out).unwrap();
std::fs::remove_file(&content).unwrap();
}
fn corpus() -> tempfile::TempDir {
let root = tempfile::tempdir().unwrap();
let dir = root.path();
pack(
dir,
"msa.pdf",
r#"title = "Master services agreement"
status = "draft"
pages = 42
tags = ["legal", "draft"]
[governance]
owner = "Kim"
"#,
);
pack(
&dir.join("2026"),
"renewal.docx",
r#"title = "Renewal, 2026"
status = "draft"
pages = 6
[governance]
owner = "Lee"
"#,
);
pack(
&dir.join("2026").join("q3"),
"q3.xlsx",
r#"title = "Q3 report"
status = "signed"
pages = 12
"#,
);
pack(
&dir.join("2026").join("q3"),
"notes.txt",
r#"title = "Field notes"
status = "draft"
pages = "many"
"#,
);
std::fs::write(dir.join("2026").join("broken.slpc"), b"not a container").unwrap();
std::fs::write(dir.join("2026").join("README.md"), b"plain").unwrap();
root
}
fn run_to_end(folder: &Path, recursive: bool, text: &str) -> Run {
let source = slipql::ast::Source {
root: folder.to_path_buf(),
recursive,
};
let query = slipql::parse_with(text, Some(&source)).unwrap();
let mut run = Run::start(&query, || {}).unwrap();
loop {
if run.drain() == Update::Finished {
return run;
}
std::thread::yield_now();
}
}
fn cells(run: &Run, column: &str) -> Vec<String> {
run.rows()
.iter()
.map(|row| filebase::query::cell(row, column))
.collect()
}
fn slashed(s: &str) -> String {
s.replace('\\', "/")
}
#[test]
fn a_bound_folder_does_not_descend_unless_asked() {
let root = corpus();
let shallow = run_to_end(root.path(), false, "select @path, title");
assert_eq!(cells(&shallow, "@path"), vec!["msa.pdf.slpc"]);
let deep = run_to_end(root.path(), true, "select @path, title");
let mut paths: Vec<String> = cells(&deep, "@path").iter().map(|p| slashed(p)).collect();
paths.sort();
assert_eq!(
paths,
vec![
"2026/q3/notes.txt.slpc",
"2026/q3/q3.xlsx.slpc",
"2026/renewal.docx.slpc",
"msa.pdf.slpc",
]
);
}
#[test]
fn a_filter_reaches_the_scan() {
let root = corpus();
let run = run_to_end(
root.path(),
true,
r#"select @path, title where status = "draft""#,
);
let mut titles = cells(&run, "title");
titles.sort();
assert_eq!(
titles,
vec!["Field notes", "Master services agreement", "Renewal, 2026"]
);
let run = run_to_end(
root.path(),
true,
r#"select title, governance.owner where status = "draft""#,
);
let owners = cells(&run, "governance.owner");
assert_eq!(owners.len(), 3);
assert_eq!(owners.iter().filter(|o| o.is_empty()).count(), 1, "{owners:?}");
}
#[test]
fn select_star_takes_the_union_over_the_rows() {
let root = corpus();
let run = run_to_end(root.path(), true, "select *");
let columns = run.columns();
assert!(columns.contains(&"@path".to_owned()), "{columns:?}");
assert!(
columns.contains(&"governance.owner".to_owned()),
"{columns:?}"
);
assert!(columns.contains(&"tags".to_owned()), "{columns:?}");
}
#[test]
fn what_the_scan_could_not_use_is_reported() {
let root = corpus();
let run = run_to_end(root.path(), true, "select @path");
let notices = run.notices().expect("a finished run has notices");
assert_eq!(notices.skipped.len(), 1, "{:?}", notices.skipped);
assert!(
notices.skipped[0].contains("broken.slpc"),
"{:?}",
notices.skipped
);
assert!(
!notices.skipped.iter().any(|line| line.contains("README")),
"{:?}",
notices.skipped
);
assert!(!notices.truncated);
assert!(run.status().contains("1 skipped"), "{}", run.status());
}
#[test]
fn a_comparison_across_types_is_counted_and_said() {
let root = corpus();
let run = run_to_end(root.path(), true, "select @path, title where pages > 10");
let mut titles = cells(&run, "title");
titles.sort();
assert_eq!(titles, vec!["Master services agreement", "Q3 report"]);
let notices = run.notices().unwrap();
assert_eq!(notices.mismatches.len(), 1, "{:?}", notices.mismatches);
let line = ¬ices.mismatches[0];
assert!(line.contains("pages"), "{line}");
assert!(line.contains("string"), "{line}");
assert!(line.contains("integer"), "{line}");
assert!(
run.status().contains("1 comparison across types"),
"{}",
run.status()
);
}
#[test]
fn a_selected_row_opens_the_container_the_row_names() {
let root = corpus();
let run = run_to_end(root.path(), true, r#"select @path where title = "Q3 report""#);
assert_eq!(run.rows().len(), 1);
let relative = run.rows()[0].path.clone();
assert_eq!(slashed(&relative), "2026/q3/q3.xlsx.slpc");
let detail = Detail::open(root.path(), &relative);
let Outcome::Read(contents) = &detail.outcome else {
panic!("the container the scan just read should open");
};
assert_eq!(contents.content.name, "q3.xlsx");
assert!(contents.content.can_be_opened());
assert_eq!(
contents.flyleaf["title"].as_str(),
Some("Q3 report"),
"the pane shows the container's own flyleaf"
);
}
#[test]
fn a_container_that_will_not_open_is_a_sentence_and_not_a_panic() {
let root = corpus();
let detail = Detail::open(root.path(), "2026/broken.slpc");
match &detail.outcome {
Outcome::Unreadable(why) => assert!(!why.is_empty()),
Outcome::Read(_) => panic!("fifteen bytes of text are not a container"),
}
assert!(detail.content().is_none());
let gone = Detail::open(root.path(), "nothing/here.slpc");
assert!(matches!(gone.outcome, Outcome::Unreadable(_)));
}
#[test]
fn a_content_file_is_handed_over_from_somewhere_else() {
let root = corpus();
let scratch = tempfile::tempdir().unwrap();
let detail = Detail::open(root.path(), "msa.pdf.slpc");
let out = detail.extract_to(scratch.path()).unwrap();
assert_eq!(
out.parent().map(|p| p.canonicalize().unwrap()),
Some(scratch.path().canonicalize().unwrap())
);
assert_eq!(out.file_name().unwrap(), "msa.pdf");
assert_eq!(
std::fs::read_to_string(&out).unwrap(),
"the bytes of msa.pdf"
);
let beside: Vec<PathBuf> = std::fs::read_dir(root.path())
.unwrap()
.map(|e| e.unwrap().path())
.collect();
assert!(
!beside.iter().any(|p| p.file_name().unwrap() == "msa.pdf"),
"nothing was written beside the container: {beside:?}"
);
}
#[test]
fn a_limit_stops_the_scan() {
let root = corpus();
let run = run_to_end(root.path(), true, "select @path limit 2");
assert_eq!(run.rows().len(), 2);
assert!(!run.notices().unwrap().truncated, "a limit is not a ceiling");
assert!(run.status().starts_with("2 rows"), "{}", run.status());
}