1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Regression test for chunk-hash caching (#1107).
//!
//! Without per-chunk hash caching, every append to a brain file re-embeds
//! the entire file (~100 chunks) because the document hash changes. With
//! chunk-hash caching, only chunks whose content actually changed are
//! re-embedded. This test pins the fix: unchanged chunks are skipped.
//!
//! These tests require vector_enabled = true and an initialized embedding engine,
//! so they are skipped when the feature is disabled.
use crate::memory::db::Store;
use std::sync::Mutex;
/// Appending one paragraph to a 3-chunk document should only re-embed
/// the changed chunk, not all 3.
///
/// Skipped when vector_enabled = false (no embedding engine available).
#[tokio::test]
#[ignore = "requires vector_enabled and embedding engine initialization"]
async fn append_skips_unchanged_chunks() {
// Create a test store with vector_enabled = true
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("test.db");
let store = Store::open(&db_path).expect("Failed to create test store");
// Bound as a shared reference on purpose. `Box::leak` yields `&'static
// mut`, which makes every `lock()` below look redundant to clippy
// (`mut_mutex_lock`) even though the code genuinely wants the mutex
// semantics — `embed_content` takes it shared and locks internally.
let store: &'static Mutex<Store> = Box::leak(Box::new(Mutex::new(store)));
// Create a 3-paragraph document (will be chunked into 3 chunks)
let body_v1 = "Paragraph one content here.\n\nParagraph two content here.\n\nParagraph three content here.";
// Embed the initial document
crate::memory::embedding::embed_content(store, body_v1).expect("First embed should succeed");
// Count embeddings after first pass
let initial_count = {
let s = store.lock().unwrap();
s.vector_stats().unwrap().vector_rows
};
assert!(initial_count > 0, "Should have embedded at least one chunk");
// Now append a fourth paragraph (only the last chunk should change)
let body_v2 = format!("{}\n\nParagraph four new content.", body_v1);
// Embed again - this should skip unchanged chunks
crate::memory::embedding::embed_content(store, &body_v2).expect("Second embed should succeed");
// Verify that embeddings were updated (chunk_hash caching worked)
// We can't easily verify the exact number of re-embeds without
// instrumentation, but we can verify the document was processed
// and the new chunks are present.
let final_count = {
let s = store.lock().unwrap();
s.vector_stats().unwrap().vector_rows
};
// The final count should be >= initial (new chunks may have been added)
assert!(
final_count >= initial_count,
"Final embedding count should be >= initial"
);
}
/// Modifying an existing chunk's content should trigger re-embedding.
///
/// Skipped when vector_enabled = false (no embedding engine available).
#[tokio::test]
#[ignore = "requires vector_enabled and embedding engine initialization"]
async fn modify_chunk_triggers_reembed() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("test.db");
let store = Store::open(&db_path).expect("Failed to create test store");
// Bound as a shared reference on purpose. `Box::leak` yields `&'static
// mut`, which makes every `lock()` below look redundant to clippy
// (`mut_mutex_lock`) even though the code genuinely wants the mutex
// semantics — `embed_content` takes it shared and locks internally.
let store: &'static Mutex<Store> = Box::leak(Box::new(Mutex::new(store)));
// Create a document with one chunk
let body_v1 = "Original paragraph content.";
crate::memory::embedding::embed_content(store, body_v1).expect("First embed should succeed");
// Modify the chunk content
let body_v2 = "Modified paragraph content with changes.";
// Embed again - this should re-embed the changed chunk
crate::memory::embedding::embed_content(store, body_v2).expect("Second embed should succeed");
// Verify embedding exists
let count = {
let s = store.lock().unwrap();
s.vector_stats().unwrap().vector_rows
};
assert!(count > 0, "Should have at least one embedding");
}
/// Identical content should not trigger re-embedding.
///
/// Skipped when vector_enabled = false (no embedding engine available).
#[tokio::test]
#[ignore = "requires vector_enabled and embedding engine initialization"]
async fn identical_content_skips_reembed() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("test.db");
let store = Store::open(&db_path).expect("Failed to create test store");
// Bound as a shared reference on purpose. `Box::leak` yields `&'static
// mut`, which makes every `lock()` below look redundant to clippy
// (`mut_mutex_lock`) even though the code genuinely wants the mutex
// semantics — `embed_content` takes it shared and locks internally.
let store: &'static Mutex<Store> = Box::leak(Box::new(Mutex::new(store)));
let body = "Same content repeated.";
// Embed twice with identical content
crate::memory::embedding::embed_content(store, body).expect("First embed should succeed");
crate::memory::embedding::embed_content(store, body).expect("Second embed should succeed");
// Verify embedding exists (should be exactly one set of chunks)
let count = {
let s = store.lock().unwrap();
s.vector_stats().unwrap().vector_rows
};
assert!(count > 0, "Should have at least one embedding");
}