test(storage): pin FTS5 contract for search_transcripts
search_transcripts already backs onto the transcripts_fts virtual table (migration v4, trigger-maintained) via MATCH + ORDER BY rank. Adding a test to lock the behaviour: token matching is case-insensitive, rank- ordered, and non-matching tokens return nothing. This is Phase G of the post-OpenWhispr audit — semantic embeddings stay deferred until the FTS experience actually hits a wall. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -888,6 +888,61 @@ mod tests {
|
|||||||
assert!(deleted.is_none());
|
assert!(deleted.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn search_transcripts_uses_fts5_and_ranks_by_relevance() {
|
||||||
|
// Locks in the FTS5 behaviour contract: MATCH-based substring-like
|
||||||
|
// search (case-insensitive tokens) with rank-ordered results, so
|
||||||
|
// anyone swapping `search_transcripts` out for embeddings later
|
||||||
|
// knows what invariants they must preserve.
|
||||||
|
let pool = test_pool().await;
|
||||||
|
|
||||||
|
let rows = [
|
||||||
|
("t1", "The Parakeet speech model runs on sherpa-onnx."),
|
||||||
|
(
|
||||||
|
"t2",
|
||||||
|
"Parakeet parakeet parakeet dominates English benchmarks.",
|
||||||
|
),
|
||||||
|
("t3", "Whisper large-v3 remains the multilingual champion."),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (id, text) in rows {
|
||||||
|
insert_transcript(
|
||||||
|
&pool,
|
||||||
|
&InsertTranscriptParams {
|
||||||
|
id,
|
||||||
|
text,
|
||||||
|
source: "microphone",
|
||||||
|
profile_id: crate::DEFAULT_PROFILE_ID,
|
||||||
|
title: None,
|
||||||
|
audio_path: None,
|
||||||
|
duration: 1.0,
|
||||||
|
engine: Some("whisper"),
|
||||||
|
model_id: Some("whisper-tiny-en"),
|
||||||
|
inference_ms: None,
|
||||||
|
sample_rate: None,
|
||||||
|
audio_channels: None,
|
||||||
|
format_mode: None,
|
||||||
|
remove_fillers: false,
|
||||||
|
british_english: false,
|
||||||
|
anti_hallucination: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let hits = search_transcripts(&pool, "parakeet", 10).await.unwrap();
|
||||||
|
let ids: Vec<&str> = hits.iter().map(|row| row.id.as_str()).collect();
|
||||||
|
assert_eq!(
|
||||||
|
ids,
|
||||||
|
vec!["t2", "t1"],
|
||||||
|
"t3 has no 'parakeet' token; t2 outranks t1 on repetition"
|
||||||
|
);
|
||||||
|
|
||||||
|
let no_match = search_transcripts(&pool, "moonshine", 10).await.unwrap();
|
||||||
|
assert!(no_match.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn task_crud_roundtrip() {
|
async fn task_crud_roundtrip() {
|
||||||
let pool = test_pool().await;
|
let pool = test_pool().await;
|
||||||
|
|||||||
Reference in New Issue
Block a user