use anyhow::Result;
use crate::db::{Database, RetrievalFilters, SearchMode, SearchResults};
use crate::model::{build_item, build_representation, build_snapshot, CaptureContext};
use super::{
analyze_query, fts_query, invalid_fts_message, is_simple_fts_query, literal_fts_match_query,
normalise_file_path,
};
pub(in crate::db) fn fake_snapshot(
change_count: i64,
text: &str,
) -> crate::model::ClipboardSnapshot {
build_snapshot(
CaptureContext::new(change_count)
.with_frontmost_app_name("Terminal")
.with_frontmost_app_bundle_id("com.apple.Terminal"),
vec![build_item(
0,
vec![build_representation(
"public.utf8-plain-text".to_string(),
None,
text.as_bytes().to_vec(),
)],
)],
)
}
pub(in crate::db) fn unfiltered() -> RetrievalFilters {
RetrievalFilters::default()
}
#[test]
pub(in crate::db) fn like_special_characters_skip_fts_mode() {
assert!(analyze_query("50%").literal_preferred);
assert!(analyze_query("config_test").literal_preferred);
assert!(analyze_query(r"logs\2024").literal_preferred);
assert!(analyze_query("https://example.com").literal_preferred);
assert!(analyze_query("com.apple.Terminal").literal_preferred);
assert!(analyze_query("~/path/with/slashes").literal_preferred);
assert!(analyze_query("foo:bar").literal_preferred);
assert!(analyze_query("git commit -m").literal_preferred);
assert!(!analyze_query("git clone").literal_preferred);
}
#[test]
pub(in crate::db) fn query_analysis_preserves_exact_phrase_without_forcing_literal() {
let analysis = analyze_query("\"launchctl bootstrap\"");
assert_eq!(
analysis.exact_phrase.as_deref(),
Some("launchctl bootstrap")
);
assert!(!analysis.literal_preferred);
}
#[test]
pub(in crate::db) fn simple_fts_query_skips_per_row_phrase_scoring() {
assert!(is_simple_fts_query(&analyze_query("git")));
assert!(!is_simple_fts_query(&analyze_query("\"git clone\"")));
assert!(!is_simple_fts_query(&analyze_query("git clone")));
assert!(!is_simple_fts_query(&analyze_query("com.apple.Terminal")));
let simple_sql = fts_query(false, false, true);
let phrase_sql = fts_query(false, false, false);
assert!(!simple_sql.contains("Phrase match in best text"));
assert!(!simple_sql.contains("THEN 0.08"));
assert!(phrase_sql.contains("Phrase match in best text"));
assert!(phrase_sql.contains("THEN 0.08"));
}
#[test]
pub(in crate::db) fn invalid_fts_messages_are_narrowly_classified() {
assert!(invalid_fts_message("fts5: syntax error near \"%\""));
assert!(invalid_fts_message("malformed MATCH expression"));
assert!(invalid_fts_message("no such column: https"));
assert!(!invalid_fts_message("database disk image is malformed"));
}
#[test]
pub(in crate::db) fn literal_fts_match_query_skips_like_escaped_inputs() {
assert!(literal_fts_match_query(&analyze_query("50%")).is_none());
assert!(literal_fts_match_query(&analyze_query("config_test")).is_none());
assert!(literal_fts_match_query(&analyze_query(r"logs\2024")).is_none());
assert_eq!(
literal_fts_match_query(&analyze_query("/tmp/repo/42/Cargo.toml")).as_deref(),
Some("\"/tmp/repo/42/cargo.toml\"")
);
assert_eq!(
analyze_query("~/path/with/slashes")
.path_fragment
.as_deref(),
Some("path/with/slashes")
);
assert_eq!(
analyze_query("file:///tmp/repo/42/Cargo.toml")
.path_fragment
.as_deref(),
Some("/tmp/repo/42/cargo.toml")
);
}
#[test]
pub(in crate::db) fn file_urls_decode_to_paths() {
assert_eq!(
normalise_file_path("file:///Users/test/Documents/hello%20world.txt"),
"/Users/test/Documents/hello world.txt"
);
assert_eq!(
normalise_file_path("/Users/test/Documents/hello world.txt"),
"/Users/test/Documents/hello world.txt"
);
}
#[test]
pub(in crate::db) fn search_auto_uses_literal_matching_for_wildcard_queries() -> Result<()> {
let mut db = Database::open_in_memory()?;
db.store_capture(&fake_snapshot(1, "Discount: 50 percent off"))?;
db.store_capture(&fake_snapshot(2, "Discount: 50%"))?;
let results = db.search_auto("50%", 10, &unfiltered())?;
assert_eq!(results.mode_used(), SearchMode::Literal);
assert_eq!(results.hits().len(), 1);
assert_eq!(results.hits()[0].preview_text(), "Discount: 50%");
Ok(())
}
#[test]
pub(in crate::db) fn find_snapshot_hydrates_nested_items_and_recent_events() -> Result<()> {
let mut db = Database::open_in_memory()?;
let snapshot = build_snapshot(
CaptureContext::new(1)
.with_frontmost_app_name("Terminal")
.with_frontmost_app_bundle_id("com.apple.Terminal"),
vec![build_item(
0,
vec![build_representation(
"public.utf8-plain-text".to_string(),
None,
b"git status".to_vec(),
)],
)],
);
let stored = db.store_capture(&snapshot)?;
db.store_capture(&snapshot)?;
let details = db
.find_snapshot(stored.snapshot_id(), 10)?
.expect("snapshot exists");
assert_eq!(details.items().len(), 1);
assert_eq!(details.recent_events().len(), 2);
Ok(())
}
#[test]
pub(in crate::db) fn empty_fts_results_fall_back_to_literal_matches() -> Result<()> {
let mut db = Database::open_in_memory()?;
db.store_capture(&fake_snapshot(1, "https://example.com/repo"))?;
let results: SearchResults = db.search_auto("https://example.com/repo", 10, &unfiltered())?;
assert_eq!(results.mode_used(), SearchMode::Literal);
assert_eq!(results.hits().len(), 1);
Ok(())
}