136 lines
4.8 KiB
Rust
136 lines
4.8 KiB
Rust
use anyhow::Result;
|
|||
|
|
use searchi_core::{IndexOptions, SearchIndex, SearchOptions};
|
||
|
|
use std::fs;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn exclusions_database_and_unchanged_metadata() -> Result<()> {
|
||
|
|
let root = tempfile::tempdir()?;
|
||
|
|
for directory in [".git", "node_modules", "target", "private"] {
|
||
|
|
fs::create_dir(root.path().join(directory))?;
|
||
|
|
fs::write(root.path().join(directory).join("hidden.txt"), "secret")?;
|
||
|
|
}
|
||
|
|
fs::write(root.path().join("visible.txt"), "original content")?;
|
||
|
|
let mut index = SearchIndex::open(root.path().join("index.db"))?;
|
||
|
|
let options = IndexOptions {
|
||
|
|
excluded_directory_names: vec![
|
||
|
|
".git".into(),
|
||
|
|
"node_modules".into(),
|
||
|
|
"target".into(),
|
||
|
|
"private".into(),
|
||
|
|
],
|
||
|
|
};
|
||
|
|
assert_eq!(
|
||
|
|
index
|
||
|
|
.index_folder_with_options(root.path(), &options)?
|
||
|
|
.indexed,
|
||
|
|
1
|
||
|
|
);
|
||
|
|
assert_eq!(index.stats()?.files, 1);
|
||
|
|
let unchanged = index.index_folder_with_options(root.path(), &options)?;
|
||
|
|
assert_eq!(unchanged.unchanged, 1);
|
||
|
|
assert_eq!(unchanged.indexed, 0);
|
||
|
|
assert!(index.search("secret", 10)?.is_empty());
|
||
|
|
assert!(index.search("index.db", 10)?.is_empty());
|
||
|
|
fs::write(
|
||
|
|
root.path().join("visible.txt"),
|
||
|
|
"changed and longer content",
|
||
|
|
)?;
|
||
|
|
assert_eq!(
|
||
|
|
index
|
||
|
|
.index_folder_with_options(root.path(), &options)?
|
||
|
|
.indexed,
|
||
|
|
1
|
||
|
|
);
|
||
|
|
assert!(index.search("original", 10)?.is_empty());
|
||
|
|
assert_eq!(index.search("changed", 10)?.len(), 1);
|
||
|
|
// A new exclusion removes previously indexed entries from this root.
|
||
|
|
let mut options = IndexOptions::default();
|
||
|
|
index.index_folder_with_options(root.path(), &options)?;
|
||
|
|
assert_eq!(index.search("secret", 10)?.len(), 1);
|
||
|
|
options.excluded_directory_names.push("private".into());
|
||
|
|
assert_eq!(
|
||
|
|
index
|
||
|
|
.index_folder_with_options(root.path(), &options)?
|
||
|
|
.removed,
|
||
|
|
1
|
||
|
|
);
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn filters_apply_before_limit_and_respect_folder_boundaries() -> Result<()> {
|
||
|
|
let root = tempfile::tempdir()?;
|
||
|
|
let docs = root.path().join("docs");
|
||
|
|
let sibling = root.path().join("docs-more");
|
||
|
|
fs::create_dir(&docs)?;
|
||
|
|
fs::create_dir(&sibling)?;
|
||
|
|
fs::write(docs.join("shared.md"), "shared short")?;
|
||
|
|
fs::write(docs.join("shared.txt"), "shared considerably longer")?;
|
||
|
|
fs::write(sibling.join("shared.md"), "shared sibling")?;
|
||
|
|
let mut index = SearchIndex::open(":memory:")?;
|
||
|
|
index.index_folder(root.path())?;
|
||
|
|
let options = SearchOptions {
|
||
|
|
folder: Some(docs),
|
||
|
|
extension: Some(".MD".into()),
|
||
|
|
max_size: Some(15),
|
||
|
|
..Default::default()
|
||
|
|
};
|
||
|
|
let results = index.search_with_options("shared", 1, &options)?;
|
||
|
|
assert_eq!(results.len(), 1);
|
||
|
|
assert_eq!(results[0].filename, "shared.md");
|
||
|
|
assert!(!results[0].path.contains("docs-more"));
|
||
|
|
let options = SearchOptions {
|
||
|
|
min_size: Some(20),
|
||
|
|
..Default::default()
|
||
|
|
};
|
||
|
|
assert_eq!(index.search_with_options("shared", 10, &options)?.len(), 1);
|
||
|
|
let options = SearchOptions {
|
||
|
|
modified_before_unix_ms: Some(0),
|
||
|
|
..Default::default()
|
||
|
|
};
|
||
|
|
assert!(index
|
||
|
|
.search_with_options("shared", 10, &options)?
|
||
|
|
.is_empty());
|
||
|
|
let options = SearchOptions {
|
||
|
|
min_size: Some(20),
|
||
|
|
max_size: Some(10),
|
||
|
|
..Default::default()
|
||
|
|
};
|
||
|
|
assert!(index.search_with_options("shared", 10, &options).is_err());
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn migrates_v1_database_without_losing_search_records() -> Result<()> {
|
||
|
|
let root = tempfile::tempdir()?;
|
||
|
|
let db = root.path().join("legacy.db");
|
||
|
|
let connection = rusqlite::Connection::open(&db)?;
|
||
|
|
connection.execute_batch(
|
||
|
|
"CREATE TABLE files(id INTEGER PRIMARY KEY, path TEXT NOT NULL UNIQUE,
|
||
|
|
root TEXT NOT NULL, filename TEXT NOT NULL, content TEXT NOT NULL);
|
||
|
|
PRAGMA user_version=1;",
|
||
|
|
)?;
|
||
|
|
drop(connection);
|
||
|
|
let mut index = SearchIndex::open(&db)?;
|
||
|
|
fs::write(root.path().join("sample.txt"), "migration evidence")?;
|
||
|
|
index.index_folder(root.path())?;
|
||
|
|
assert_eq!(index.search("evidence", 10)?.len(), 1);
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn readers_open_and_search_during_uncommitted_writer() -> Result<()> {
|
||
|
|
let root = tempfile::tempdir()?;
|
||
|
|
let db = root.path().join("index.db");
|
||
|
|
fs::write(root.path().join("notes.txt"), "committed evidence")?;
|
||
|
|
let mut index = SearchIndex::open(&db)?;
|
||
|
|
index.index_folder(root.path())?;
|
||
|
|
let writer = rusqlite::Connection::open(&db)?;
|
||
|
|
writer.execute_batch("BEGIN IMMEDIATE; UPDATE files SET content='uncommitted replacement';")?;
|
||
|
|
// Opening a current schema must not issue DDL or acquire a write lock.
|
||
|
|
let reader = SearchIndex::open(&db)?;
|
||
|
|
assert_eq!(reader.search("evidence", 10)?.len(), 1);
|
||
|
|
assert!(reader.search("replacement", 10)?.is_empty());
|
||
|
|
writer.execute_batch("ROLLBACK;")?;
|
||
|
|
Ok(())
|
||
|
|
}
|