use std::path::Path;
use anyhow::Result;
use dowse::{IndexUpdater, PendingChange, PendingOp, Searcher, rebuild_index};
mod common;
fn target_dir() -> tempfile::TempDir {
tempfile::Builder::new()
.prefix("dowse-upd-")
.tempdir()
.unwrap()
}
fn upsert(path: &Path) -> PendingChange {
PendingChange {
path: path.to_path_buf(),
op: PendingOp::Upsert,
}
}
fn remove(path: &Path) -> PendingChange {
PendingChange {
path: path.to_path_buf(),
op: PendingOp::Remove,
}
}
fn upsert_tree(path: &Path) -> PendingChange {
PendingChange {
path: path.to_path_buf(),
op: PendingOp::UpsertTree,
}
}
fn count_hits(index_dir: &Path, query: &str) -> usize {
let searcher = Searcher::open(index_dir).unwrap();
searcher.search(query, 50).unwrap().len()
}
#[test]
fn modify_file_updates_searchable_content() -> Result<()> {
common::force_slow_lane_for_tests();
let index_dir = tempfile::tempdir()?;
let target = target_dir();
let file = target.path().join("note.md");
std::fs::write(&file, "最初的内容讲的是苹果")?;
rebuild_index(index_dir.path(), target.path())?;
assert_eq!(count_hits(index_dir.path(), "苹果"), 1, "初始内容应能搜到");
assert_eq!(count_hits(index_dir.path(), "香蕉"), 0);
std::fs::write(&file, "改过之后讲的是香蕉")?;
let mut updater = IndexUpdater::open(index_dir.path())?;
let outcome = updater.apply(&[upsert(&file)])?;
assert_eq!(outcome.upserted, 1);
assert_eq!(count_hits(index_dir.path(), "香蕉"), 1, "新内容应能搜到");
assert_eq!(count_hits(index_dir.path(), "苹果"), 0, "旧内容应搜不到");
drop(updater);
common::close_tempdir_retrying(index_dir);
common::close_tempdir_retrying(target);
Ok(())
}
#[test]
fn delete_file_removes_it_from_index() -> Result<()> {
common::force_slow_lane_for_tests();
let index_dir = tempfile::tempdir()?;
let target = target_dir();
let file = target.path().join("doomed.md");
std::fs::write(&file, "待删除的文档独有词汇 zzqq")?;
rebuild_index(index_dir.path(), target.path())?;
assert_eq!(count_hits(index_dir.path(), "zzqq"), 1);
std::fs::remove_file(&file)?;
let mut updater = IndexUpdater::open(index_dir.path())?;
let outcome = updater.apply(&[remove(&file)])?;
assert_eq!(outcome.removed, 1);
assert_eq!(count_hits(index_dir.path(), "zzqq"), 0, "删除后应搜不到");
drop(updater);
common::close_tempdir_retrying(index_dir);
common::close_tempdir_retrying(target);
Ok(())
}
#[test]
fn rename_file_old_name_gone_new_name_and_content_searchable() -> Result<()> {
common::force_slow_lane_for_tests();
let index_dir = tempfile::tempdir()?;
let target = target_dir();
let old = target.path().join("oldname.md");
let new = target.path().join("newname.md");
std::fs::write(&old, "改名测试的正文内容 mango")?;
rebuild_index(index_dir.path(), target.path())?;
assert_eq!(
count_hits(index_dir.path(), "oldname"),
1,
"旧文件名应能搜到"
);
std::fs::rename(&old, &new)?;
let mut updater = IndexUpdater::open(index_dir.path())?;
updater.apply(&[remove(&old), upsert(&new)])?;
assert_eq!(
count_hits(index_dir.path(), "oldname"),
0,
"旧文件名应搜不到"
);
assert_eq!(
count_hits(index_dir.path(), "newname"),
1,
"新文件名应能搜到"
);
assert_eq!(count_hits(index_dir.path(), "mango"), 1, "正文内容照常命中");
drop(updater);
common::close_tempdir_retrying(index_dir);
common::close_tempdir_retrying(target);
Ok(())
}
#[test]
fn remove_tree_prefix_deletes_whole_subdirectory() -> Result<()> {
common::force_slow_lane_for_tests();
let index_dir = tempfile::tempdir()?;
let target = target_dir();
let sub = target.path().join("sub");
let sub2 = target.path().join("sub2");
std::fs::create_dir(&sub)?;
std::fs::create_dir(&sub2)?;
std::fs::write(sub.join("a.md"), "子目录文件甲 alpha")?;
std::fs::write(sub.join("b.md"), "子目录文件乙 alpha")?;
std::fs::write(sub2.join("c.md"), "兄弟目录文件丙 alpha")?;
std::fs::write(target.path().join("top.md"), "顶层文件 alpha")?;
rebuild_index(index_dir.path(), target.path())?;
assert_eq!(
count_hits(index_dir.path(), "alpha"),
4,
"初始四篇都含 alpha"
);
let mut updater = IndexUpdater::open(index_dir.path())?;
let outcome = updater.apply(&[PendingChange {
path: sub.clone(),
op: PendingOp::RemoveTree,
}])?;
assert_eq!(outcome.removed, 2, "应恰好圈选删掉 sub 下的两篇");
assert_eq!(
count_hits(index_dir.path(), "alpha"),
2,
"删完 sub 只剩兄弟目录和顶层两篇"
);
assert_eq!(
count_hits(index_dir.path(), "丙"),
1,
"兄弟目录 sub2 不受影响"
);
assert_eq!(count_hits(index_dir.path(), "顶层"), 1, "顶层文件不受影响");
assert_eq!(count_hits(index_dir.path(), "甲"), 0, "sub 下文件已删");
drop(updater);
common::close_tempdir_retrying(index_dir);
common::close_tempdir_retrying(target);
Ok(())
}
#[test]
fn upsert_tree_expands_directory_into_every_file_inside() -> Result<()> {
common::force_slow_lane_for_tests();
let index_dir = tempfile::tempdir()?;
let target = target_dir();
rebuild_index(index_dir.path(), target.path())?;
assert_eq!(count_hits(index_dir.path(), "watermelon"), 0);
let moved_in = target.path().join("moved-in");
std::fs::create_dir(&moved_in)?;
std::fs::write(moved_in.join("a.md"), "子文件甲 watermelon")?;
std::fs::write(moved_in.join("b.md"), "子文件乙 watermelon")?;
std::fs::create_dir(moved_in.join("nested"))?;
std::fs::write(
moved_in.join("nested").join("c.md"),
"嵌套子文件 watermelon",
)?;
let mut updater = IndexUpdater::open(index_dir.path())?;
let outcome = updater.apply(&[upsert_tree(&moved_in)])?;
assert_eq!(
outcome.upserted, 3,
"目录下三个文件(含嵌套)都应被展开收录"
);
assert_eq!(
count_hits(index_dir.path(), "watermelon"),
3,
"整个移入目录下的文件都应变为可搜索"
);
drop(updater);
common::close_tempdir_retrying(index_dir);
common::close_tempdir_retrying(target);
Ok(())
}