chore: index slicing and lowercase errors

This commit is contained in:
Per Stark
2026-05-27 12:41:26 +02:00
parent 2d630e2af9
commit 890a4b381d
21 changed files with 446 additions and 182 deletions
+4 -2
View File
@@ -204,8 +204,9 @@ impl SurrealDbClient {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use anyhow::{self, Context}; #![allow(clippy::expect_used, clippy::must_use_candidate)]
use crate::stored_object; use crate::stored_object;
use anyhow::{self, Context};
use super::*; use super::*;
use uuid::Uuid; use uuid::Uuid;
@@ -300,7 +301,8 @@ mod tests {
.get_item(&dummy.id) .get_item(&dummy.id)
.await .await
.with_context(|| "fetch after upsert".to_string())?; .with_context(|| "fetch after upsert".to_string())?;
let fetched = fetched.ok_or_else(|| anyhow::anyhow!("Expected record to exist after upsert"))?; let fetched =
fetched.ok_or_else(|| anyhow::anyhow!("Expected record to exist after upsert"))?;
assert_eq!(fetched.name, "updated"); assert_eq!(fetched.name, "updated");
let new_record = Dummy { let new_record = Dummy {
+17 -14
View File
@@ -175,10 +175,7 @@ pub async fn rebuild(db: &SurrealDbClient) -> Result<(), AppError> {
.map_err(|err| AppError::InternalError(err.to_string())) .map_err(|err| AppError::InternalError(err.to_string()))
} }
async fn ensure_runtime_inner( async fn ensure_runtime_inner(db: &SurrealDbClient, embedding_dimension: usize) -> Result<()> {
db: &SurrealDbClient,
embedding_dimension: usize,
) -> Result<()> {
create_fts_analyzer(db).await?; create_fts_analyzer(db).await?;
for spec in fts_index_specs() { for spec in fts_index_specs() {
@@ -629,7 +626,10 @@ fn parse_index_build_info(
if total == 0 { if total == 0 {
0.0 0.0
} else { } else {
((f64::from(u32::try_from(processed).unwrap_or(u32::MAX)) / f64::from(u32::try_from(total).unwrap_or(1))).min(1.0)) * 100.0 ((f64::from(u32::try_from(processed).unwrap_or(u32::MAX))
/ f64::from(u32::try_from(total).unwrap_or(1)))
.min(1.0))
* 100.0
} }
}); });
@@ -698,8 +698,9 @@ async fn index_exists(db: &SurrealDbClient, table: &str, index_name: &str) -> Re
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use anyhow::{self, Context}; #![allow(clippy::expect_used, clippy::must_use_candidate)]
use crate::storage::db::SurrealDbClient; use crate::storage::db::SurrealDbClient;
use anyhow::{self, Context};
use serde_json::json; use serde_json::json;
use uuid::Uuid; use uuid::Uuid;
@@ -716,8 +717,7 @@ mod tests {
} }
}); });
let snapshot = parse_index_build_info(Some(info), Some(61081)) let snapshot = parse_index_build_info(Some(info), Some(61081)).context("snapshot")?;
.context("snapshot")?;
assert_eq!( assert_eq!(
snapshot, snapshot,
IndexBuildSnapshot { IndexBuildSnapshot {
@@ -738,8 +738,7 @@ mod tests {
fn parse_index_build_info_defaults_to_ready_when_no_building_block() -> anyhow::Result<()> { fn parse_index_build_info_defaults_to_ready_when_no_building_block() -> anyhow::Result<()> {
// Surreal returns `{}` when the index exists but isn't building. // Surreal returns `{}` when the index exists but isn't building.
let info = json!({}); let info = json!({});
let snapshot = parse_index_build_info(Some(info), Some(10)) let snapshot = parse_index_build_info(Some(info), Some(10)).context("snapshot")?;
.context("snapshot")?;
assert!(snapshot.is_ready()); assert!(snapshot.is_ready());
assert_eq!(snapshot.processed, 0); assert_eq!(snapshot.processed, 0);
assert_eq!(snapshot.progress_pct, Some(0.0)); assert_eq!(snapshot.progress_pct, Some(0.0));
@@ -764,9 +763,11 @@ mod tests {
.await .await
.context("migrations should succeed")?; .context("migrations should succeed")?;
ensure_runtime(&db, 1536).await ensure_runtime(&db, 1536)
.await
.context("first call should succeed")?; .context("first call should succeed")?;
ensure_runtime(&db, 1536).await ensure_runtime(&db, 1536)
.await
.context("second index creation")?; .context("second index creation")?;
Ok(()) Ok(())
} }
@@ -783,9 +784,11 @@ mod tests {
.await .await
.context("migrations should succeed")?; .context("migrations should succeed")?;
ensure_runtime(&db, 1536).await ensure_runtime(&db, 1536)
.await
.context("initial index creation")?; .context("initial index creation")?;
ensure_runtime(&db, 128).await ensure_runtime(&db, 128)
.await
.context("overwritten index creation")?; .context("overwritten index creation")?;
Ok(()) Ok(())
} }
+115 -35
View File
@@ -275,6 +275,7 @@ async fn create_storage_backend(
/// automatic memory backend setup and proper test isolation. /// automatic memory backend setup and proper test isolation.
#[cfg(test)] #[cfg(test)]
pub mod testing { pub mod testing {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use super::*; use super::*;
use crate::utils::config::{AppConfig, PdfIngestMode}; use crate::utils::config::{AppConfig, PdfIngestMode};
use uuid; use uuid;
@@ -450,10 +451,7 @@ pub mod testing {
None None
}; };
Ok(Self { Ok(Self { storage, temp_dir })
storage,
temp_dir,
})
} }
/// Get a reference to the underlying StorageManager. /// Get a reference to the underlying StorageManager.
@@ -581,9 +579,10 @@ pub fn split_object_path(path: &str) -> AnyResult<(String, String)> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use super::*; use super::*;
use anyhow::Context;
use crate::utils::config::{PdfIngestMode::LlmFirst, StorageKind}; use crate::utils::config::{PdfIngestMode::LlmFirst, StorageKind};
use anyhow::Context;
use bytes::Bytes; use bytes::Bytes;
use uuid::Uuid; use uuid::Uuid;
@@ -637,14 +636,23 @@ mod tests {
.put(location, Bytes::from(data.to_vec())) .put(location, Bytes::from(data.to_vec()))
.await .await
.with_context(|| "put".to_string())?; .with_context(|| "put".to_string())?;
let retrieved = storage.get(location).await.with_context(|| "get".to_string())?; let retrieved = storage
.get(location)
.await
.with_context(|| "get".to_string())?;
assert_eq!(retrieved.as_ref(), data); assert_eq!(retrieved.as_ref(), data);
// Test exists // Test exists
assert!(storage.exists(location).await.with_context(|| "exists check".to_string())?); assert!(storage
.exists(location)
.await
.with_context(|| "exists check".to_string())?);
// Test delete // Test delete
storage.delete_prefix("test/data/").await.with_context(|| "delete".to_string())?; storage
.delete_prefix("test/data/")
.await
.with_context(|| "delete".to_string())?;
assert!(!storage assert!(!storage
.exists(location) .exists(location)
.await .await
@@ -674,7 +682,10 @@ mod tests {
.put(location, Bytes::from(data.to_vec())) .put(location, Bytes::from(data.to_vec()))
.await .await
.with_context(|| "put".to_string())?; .with_context(|| "put".to_string())?;
let retrieved = storage.get(location).await.with_context(|| "get".to_string())?; let retrieved = storage
.get(location)
.await
.with_context(|| "get".to_string())?;
assert_eq!(retrieved.as_ref(), data); assert_eq!(retrieved.as_ref(), data);
let object_dir = resolved_base.join("test/data"); let object_dir = resolved_base.join("test/data");
@@ -683,10 +694,16 @@ mod tests {
.with_context(|| "object directory exists after write".to_string())?; .with_context(|| "object directory exists after write".to_string())?;
// Test exists // Test exists
assert!(storage.exists(location).await.with_context(|| "exists check".to_string())?); assert!(storage
.exists(location)
.await
.with_context(|| "exists check".to_string())?);
// Test delete // Test delete
storage.delete_prefix("test/data/").await.with_context(|| "delete".to_string())?; storage
.delete_prefix("test/data/")
.await
.with_context(|| "delete".to_string())?;
assert!(!storage assert!(!storage
.exists(location) .exists(location)
.await .await
@@ -723,7 +740,10 @@ mod tests {
.with_context(|| "put first".to_string())?; .with_context(|| "put first".to_string())?;
// Retrieve and verify first data // Retrieve and verify first data
let retrieved1 = storage.get(location).await.with_context(|| "get first".to_string())?; let retrieved1 = storage
.get(location)
.await
.with_context(|| "get first".to_string())?;
assert_eq!(retrieved1.as_ref(), data1); assert_eq!(retrieved1.as_ref(), data1);
// Overwrite with second data // Overwrite with second data
@@ -733,7 +753,10 @@ mod tests {
.with_context(|| "put second".to_string())?; .with_context(|| "put second".to_string())?;
// Retrieve and verify second data // Retrieve and verify second data
let retrieved2 = storage.get(location).await.with_context(|| "get second".to_string())?; let retrieved2 = storage
.get(location)
.await
.with_context(|| "get second".to_string())?;
assert_eq!(retrieved2.as_ref(), data2); assert_eq!(retrieved2.as_ref(), data2);
// Data persists across multiple operations using the same StorageManager // Data persists across multiple operations using the same StorageManager
@@ -764,11 +787,17 @@ mod tests {
} }
// Test listing without prefix // Test listing without prefix
let all_files = storage.list(None).await.with_context(|| "list all".to_string())?; let all_files = storage
.list(None)
.await
.with_context(|| "list all".to_string())?;
assert_eq!(all_files.len(), 3); assert_eq!(all_files.len(), 3);
// Test listing with prefix // Test listing with prefix
let dir1_files = storage.list(Some("dir1/")).await.with_context(|| "list dir1".to_string())?; let dir1_files = storage
.list(Some("dir1/"))
.await
.with_context(|| "list dir1".to_string())?;
assert_eq!(dir1_files.len(), 2); assert_eq!(dir1_files.len(), 2);
assert!(dir1_files assert!(dir1_files
.iter() .iter()
@@ -804,7 +833,10 @@ mod tests {
.with_context(|| "put large data".to_string())?; .with_context(|| "put large data".to_string())?;
// Get as stream // Get as stream
let mut stream = storage.get_stream(location).await.with_context(|| "get stream".to_string())?; let mut stream = storage
.get_stream(location)
.await
.with_context(|| "get stream".to_string())?;
let mut collected = Vec::new(); let mut collected = Vec::new();
while let Some(chunk) = stream.next().await { while let Some(chunk) = stream.next().await {
@@ -833,10 +865,16 @@ mod tests {
.put(location, Bytes::from(data.to_vec())) .put(location, Bytes::from(data.to_vec()))
.await .await
.with_context(|| "put".to_string())?; .with_context(|| "put".to_string())?;
let retrieved = storage.get(location).await.with_context(|| "get".to_string())?; let retrieved = storage
.get(location)
.await
.with_context(|| "get".to_string())?;
assert_eq!(retrieved.as_ref(), data); assert_eq!(retrieved.as_ref(), data);
assert!(storage.exists(location).await.with_context(|| "exists".to_string())?); assert!(storage
.exists(location)
.await
.with_context(|| "exists".to_string())?);
assert_eq!(*storage.backend_kind(), StorageKind::Memory); assert_eq!(*storage.backend_kind(), StorageKind::Memory);
Ok(()) Ok(())
@@ -879,12 +917,21 @@ mod tests {
let data = b"test data with TestStorageManager"; let data = b"test data with TestStorageManager";
// Test put and get // Test put and get
test_storage.put(location, data).await.with_context(|| "put".to_string())?; test_storage
let retrieved = test_storage.get(location).await.with_context(|| "get".to_string())?; .put(location, data)
.await
.with_context(|| "put".to_string())?;
let retrieved = test_storage
.get(location)
.await
.with_context(|| "get".to_string())?;
assert_eq!(retrieved.as_ref(), data); assert_eq!(retrieved.as_ref(), data);
// Test existence check // Test existence check
assert!(test_storage.exists(location).await.with_context(|| "exists".to_string())?); assert!(test_storage
.exists(location)
.await
.with_context(|| "exists".to_string())?);
// Test list // Test list
let files = test_storage let files = test_storage
@@ -915,13 +962,19 @@ mod tests {
let location = "test/local/file.txt"; let location = "test/local/file.txt";
let data = b"test data with local TestStorageManager"; let data = b"test data with local TestStorageManager";
test_storage.put(location, data).await test_storage
.put(location, data)
.await
.with_context(|| "put".to_string())?; .with_context(|| "put".to_string())?;
let retrieved = test_storage.get(location).await let retrieved = test_storage
.get(location)
.await
.with_context(|| "get".to_string())?; .with_context(|| "get".to_string())?;
assert_eq!(retrieved.as_ref(), data); assert_eq!(retrieved.as_ref(), data);
assert!(test_storage.exists(location).await assert!(test_storage
.exists(location)
.await
.with_context(|| "exists".to_string())?); .with_context(|| "exists".to_string())?);
Ok(()) Ok(())
@@ -940,14 +993,22 @@ mod tests {
let data1 = b"storage 1 data"; let data1 = b"storage 1 data";
let data2 = b"storage 2 data"; let data2 = b"storage 2 data";
storage1.put(location, data1).await storage1
.put(location, data1)
.await
.with_context(|| "put storage 1".to_string())?; .with_context(|| "put storage 1".to_string())?;
storage2.put(location, data2).await storage2
.put(location, data2)
.await
.with_context(|| "put storage 2".to_string())?; .with_context(|| "put storage 2".to_string())?;
let retrieved1 = storage1.get(location).await let retrieved1 = storage1
.get(location)
.await
.with_context(|| "get storage 1".to_string())?; .with_context(|| "get storage 1".to_string())?;
let retrieved2 = storage2.get(location).await let retrieved2 = storage2
.get(location)
.await
.with_context(|| "get storage 2".to_string())?; .with_context(|| "get storage 2".to_string())?;
assert_eq!(retrieved1.as_ref(), data1); assert_eq!(retrieved1.as_ref(), data1);
@@ -967,9 +1028,13 @@ mod tests {
let location = "config/test.txt"; let location = "config/test.txt";
let data = b"test data with custom config"; let data = b"test data with custom config";
test_storage.put(location, data).await test_storage
.put(location, data)
.await
.with_context(|| "put".to_string())?; .with_context(|| "put".to_string())?;
let retrieved = test_storage.get(location).await let retrieved = test_storage
.get(location)
.await
.with_context(|| "get".to_string())?; .with_context(|| "get".to_string())?;
assert_eq!(retrieved.as_ref(), data); assert_eq!(retrieved.as_ref(), data);
@@ -1000,11 +1065,17 @@ mod tests {
} }
// Test get // Test get
let retrieved = storage.get(&location).await.with_context(|| "get".to_string())?; let retrieved = storage
.get(&location)
.await
.with_context(|| "get".to_string())?;
assert_eq!(retrieved.as_ref(), data); assert_eq!(retrieved.as_ref(), data);
// Test exists // Test exists
assert!(storage.exists(&location).await.with_context(|| "exists".to_string())?); assert!(storage
.exists(&location)
.await
.with_context(|| "exists".to_string())?);
// Test delete // Test delete
storage storage
@@ -1040,11 +1111,17 @@ mod tests {
// List with prefix // List with prefix
let list_prefix = format!("{prefix}/"); let list_prefix = format!("{prefix}/");
let items = storage.list(Some(&list_prefix)).await.with_context(|| "list".to_string())?; let items = storage
.list(Some(&list_prefix))
.await
.with_context(|| "list".to_string())?;
assert_eq!(items.len(), 3); assert_eq!(items.len(), 3);
// Cleanup // Cleanup
storage.delete_prefix(&list_prefix).await.with_context(|| "cleanup".to_string())?; storage
.delete_prefix(&list_prefix)
.await
.with_context(|| "cleanup".to_string())?;
Ok(()) Ok(())
} }
@@ -1063,7 +1140,10 @@ mod tests {
return Ok(()); return Ok(());
} }
let mut stream = storage.get_stream(&location).await.with_context(|| "get stream".to_string())?; let mut stream = storage
.get_stream(&location)
.await
.with_context(|| "get stream".to_string())?;
let mut collected = Vec::new(); let mut collected = Vec::new();
while let Some(chunk) = stream.next().await { while let Some(chunk) = stream.next().await {
collected.extend_from_slice(&chunk.with_context(|| "chunk".to_string())?); collected.extend_from_slice(&chunk.with_context(|| "chunk".to_string())?);
+1
View File
@@ -88,6 +88,7 @@ impl Analytics {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use super::*; use super::*;
use crate::stored_object; use crate::stored_object;
use anyhow::{self}; use anyhow::{self};
+25 -15
View File
@@ -11,6 +11,7 @@ stored_object!(Conversation, "conversation", {
}); });
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[allow(clippy::module_name_repetitions)]
pub struct SidebarConversation { pub struct SidebarConversation {
#[serde(deserialize_with = "deserialize_sidebar_id")] #[serde(deserialize_with = "deserialize_sidebar_id")]
pub id: String, pub id: String,
@@ -144,8 +145,9 @@ impl Conversation {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use anyhow::{self, Context}; #![allow(clippy::expect_used, clippy::must_use_candidate)]
use crate::storage::types::message::MessageRole; use crate::storage::types::message::MessageRole;
use anyhow::{self, Context};
use super::*; use super::*;
@@ -173,7 +175,8 @@ mod tests {
.await .await
.with_context(|| "Failed to retrieve conversation".to_string())?; .with_context(|| "Failed to retrieve conversation".to_string())?;
let retrieved = retrieved.ok_or_else(|| anyhow::anyhow!("Expected conversation to exist"))?; let retrieved =
retrieved.ok_or_else(|| anyhow::anyhow!("Expected conversation to exist"))?;
assert_eq!(retrieved.id, conversation.id); assert_eq!(retrieved.id, conversation.id);
assert_eq!(retrieved.user_id, user_id); assert_eq!(retrieved.user_id, user_id);
assert_eq!(retrieved.title, title); assert_eq!(retrieved.title, title);
@@ -354,12 +357,15 @@ mod tests {
.expect("Failed to get sidebar conversations"); .expect("Failed to get sidebar conversations");
assert_eq!(sidebar_items.len(), 3); assert_eq!(sidebar_items.len(), 3);
assert_eq!(sidebar_items[0].id, newest.id); let s0 = sidebar_items.first().expect("expected 3 items");
assert_eq!(sidebar_items[0].title, "Newest"); let s1 = sidebar_items.get(1).expect("expected 3 items");
assert_eq!(sidebar_items[1].id, middle.id); let s2 = sidebar_items.get(2).expect("expected 3 items");
assert_eq!(sidebar_items[1].title, "Middle"); assert_eq!(s0.id, newest.id);
assert_eq!(sidebar_items[2].id, oldest.id); assert_eq!(s0.title, "Newest");
assert_eq!(sidebar_items[2].title, "Oldest"); assert_eq!(s1.id, middle.id);
assert_eq!(s1.title, "Middle");
assert_eq!(s2.id, oldest.id);
assert_eq!(s2.title, "Oldest");
} }
#[tokio::test] #[tokio::test]
@@ -389,7 +395,8 @@ mod tests {
let before_patch = Conversation::get_user_sidebar_conversations(user_id, &db) let before_patch = Conversation::get_user_sidebar_conversations(user_id, &db)
.await .await
.expect("Failed to get sidebar conversations before patch"); .expect("Failed to get sidebar conversations before patch");
assert_eq!(before_patch[0].id, second.id); let before = before_patch.first().expect("expected at least 1 item");
assert_eq!(before.id, second.id);
Conversation::patch_title(&first.id, user_id, "First (renamed)", &db) Conversation::patch_title(&first.id, user_id, "First (renamed)", &db)
.await .await
@@ -398,8 +405,9 @@ mod tests {
let after_patch = Conversation::get_user_sidebar_conversations(user_id, &db) let after_patch = Conversation::get_user_sidebar_conversations(user_id, &db)
.await .await
.expect("Failed to get sidebar conversations after patch"); .expect("Failed to get sidebar conversations after patch");
assert_eq!(after_patch[0].id, first.id); let after = after_patch.first().expect("expected at least 1 item");
assert_eq!(after_patch[0].title, "First (renamed)"); assert_eq!(after.id, first.id);
assert_eq!(after.title, "First (renamed)");
} }
#[tokio::test] #[tokio::test]
@@ -451,8 +459,8 @@ mod tests {
Conversation::get_complete_conversation(&conversation_id, user_id_1, &db).await; Conversation::get_complete_conversation(&conversation_id, user_id_1, &db).await;
assert!(result.is_ok(), "Failed to retrieve complete conversation"); assert!(result.is_ok(), "Failed to retrieve complete conversation");
let (retrieved_conversation, retrieved_messages) = result let (retrieved_conversation, retrieved_messages) =
.with_context(|| "Failed to retrieve complete conversation".to_string())?; result.with_context(|| "Failed to retrieve complete conversation".to_string())?;
assert_eq!(retrieved_conversation.id, conversation_id); assert_eq!(retrieved_conversation.id, conversation_id);
assert_eq!(retrieved_conversation.user_id, user_id_1); assert_eq!(retrieved_conversation.user_id, user_id_1);
@@ -460,8 +468,10 @@ mod tests {
assert_eq!(retrieved_messages.len(), 3); assert_eq!(retrieved_messages.len(), 3);
let message_contents: Vec<&str> = let message_contents: Vec<&str> = retrieved_messages
retrieved_messages.iter().map(|m| m.content.as_str()).collect(); .iter()
.map(|m| m.content.as_str())
.collect();
assert!(message_contents.contains(&"Hello, AI!")); assert!(message_contents.contains(&"Hello, AI!"));
assert!(message_contents.contains(&"Hello, human! How can I help you today?")); assert!(message_contents.contains(&"Hello, human! How can I help you today?"));
assert!(message_contents.contains(&"Tell me about Rust programming.")); assert!(message_contents.contains(&"Tell me about Rust programming."));
+35 -13
View File
@@ -320,6 +320,7 @@ impl FileInfo {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use anyhow::{self, Context}; use anyhow::{self, Context};
use super::*; use super::*;
@@ -330,8 +331,12 @@ mod tests {
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
/// Creates a test temporary file with the given content /// Creates a test temporary file with the given content
fn create_test_file(content: &[u8], file_name: &str) -> anyhow::Result<FieldData<NamedTempFile>> { fn create_test_file(
let mut temp_file = NamedTempFile::new().with_context(|| "Failed to create temp file".to_string())?; content: &[u8],
file_name: &str,
) -> anyhow::Result<FieldData<NamedTempFile>> {
let mut temp_file =
NamedTempFile::new().with_context(|| "Failed to create temp file".to_string())?;
temp_file temp_file
.write_all(content) .write_all(content)
.with_context(|| "Failed to write to temp file".to_string())?; .with_context(|| "Failed to write to temp file".to_string())?;
@@ -356,7 +361,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
let content = b"This is a test file for StorageManager operations"; let content = b"This is a test file for StorageManager operations";
let file_name = "storage_manager_test.txt"; let file_name = "storage_manager_test.txt";
@@ -411,7 +418,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
let content = b"filename sanitization"; let content = b"filename sanitization";
let original_name = "Complex name (1).txt"; let original_name = "Complex name (1).txt";
@@ -444,7 +453,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
let content = b"This is a test file for StorageManager duplicate detection"; let content = b"This is a test file for StorageManager duplicate detection";
let file_name = "storage_manager_duplicate.txt"; let file_name = "storage_manager_duplicate.txt";
@@ -462,7 +473,9 @@ mod tests {
let original_file_info = let original_file_info =
FileInfo::new_with_storage(field_data, &db, user_id, test_storage.storage()) FileInfo::new_with_storage(field_data, &db, user_id, test_storage.storage())
.await .await
.with_context(|| "Failed to create original file with StorageManager".to_string())?; .with_context(|| {
"Failed to create original file with StorageManager".to_string()
})?;
// Create another file with the same content but different name // Create another file with the same content but different name
let duplicate_name = "storage_manager_duplicate_2.txt"; let duplicate_name = "storage_manager_duplicate_2.txt";
@@ -472,7 +485,9 @@ mod tests {
let duplicate_file_info = let duplicate_file_info =
FileInfo::new_with_storage(field_data2, &db, user_id, test_storage.storage()) FileInfo::new_with_storage(field_data2, &db, user_id, test_storage.storage())
.await .await
.with_context(|| "Failed to process duplicate file with StorageManager".to_string())?; .with_context(|| {
"Failed to process duplicate file with StorageManager".to_string()
})?;
// Verify duplicate detection worked // Verify duplicate detection worked
assert_eq!(duplicate_file_info.id, original_file_info.id); assert_eq!(duplicate_file_info.id, original_file_info.id);
@@ -520,8 +535,7 @@ mod tests {
.await .await
.with_context(|| "create test storage manager".to_string())?; .with_context(|| "create test storage manager".to_string())?;
let file_info = let file_info =
FileInfo::new_with_storage(field_data, &db, user_id, test_storage.storage()) FileInfo::new_with_storage(field_data, &db, user_id, test_storage.storage()).await?;
.await?;
// Check essential properties // Check essential properties
assert!(!file_info.id.is_empty()); assert!(!file_info.id.is_empty());
@@ -876,7 +890,9 @@ mod tests {
let db = SurrealDbClient::memory("test_ns", "test_file_storage_memory") let db = SurrealDbClient::memory("test_ns", "test_file_storage_memory")
.await .await
.with_context(|| "Failed to start DB".to_string())?; .with_context(|| "Failed to start DB".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
let content = b"This is a test file for StorageManager"; let content = b"This is a test file for StorageManager";
let field_data = create_test_file(content, "test_storage.txt")?; let field_data = create_test_file(content, "test_storage.txt")?;
@@ -922,7 +938,9 @@ mod tests {
let db = SurrealDbClient::memory("test_ns", "test_file_storage_local") let db = SurrealDbClient::memory("test_ns", "test_file_storage_local")
.await .await
.with_context(|| "Failed to start DB".to_string())?; .with_context(|| "Failed to start DB".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
let content = b"This is a test file for StorageManager with local storage"; let content = b"This is a test file for StorageManager with local storage";
let field_data = create_test_file(content, "test_local.txt")?; let field_data = create_test_file(content, "test_local.txt")?;
@@ -968,7 +986,9 @@ mod tests {
let db = SurrealDbClient::memory("test_ns", "test_file_persistence") let db = SurrealDbClient::memory("test_ns", "test_file_persistence")
.await .await
.with_context(|| "Failed to start DB".to_string())?; .with_context(|| "Failed to start DB".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
let content = b"Test content for persistence"; let content = b"Test content for persistence";
let field_data = create_test_file(content, "persistence_test.txt")?; let field_data = create_test_file(content, "persistence_test.txt")?;
@@ -1015,7 +1035,9 @@ mod tests {
let db = SurrealDbClient::memory("test_ns", "test_file_equivalence") let db = SurrealDbClient::memory("test_ns", "test_file_equivalence")
.await .await
.with_context(|| "Failed to start DB".to_string())?; .with_context(|| "Failed to start DB".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
let content = b"Test content for equivalence testing"; let content = b"Test content for equivalence testing";
let field_data1 = create_test_file(content, "equivalence_test_1.txt")?; let field_data1 = create_test_file(content, "equivalence_test_1.txt")?;
@@ -103,6 +103,7 @@ impl IngestionPayload {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use anyhow::{self, Context}; use anyhow::{self, Context};
use chrono::Utc; use chrono::Utc;
+49 -24
View File
@@ -247,11 +247,9 @@ impl IngestionTask {
"#; "#;
debug_assert!(lifecycle::pending().reserve().is_ok()); debug_assert!(lifecycle::pending().reserve().is_ok());
debug_assert!( debug_assert!(lifecycle::pending().reserve().is_ok_and(|m| m
lifecycle::pending() .start_processing()
.reserve() .is_ok_and(|m| m.fail().is_ok_and(|m| m.reserve().is_ok()))));
.is_ok_and(|m| m.start_processing().is_ok_and(|m| m.fail().is_ok_and(|m| m.reserve().is_ok())))
);
let mut result = db let mut result = db
.client .client
@@ -277,7 +275,10 @@ impl IngestionTask {
.bind(("reserved_state", TaskState::Reserved.as_str())) .bind(("reserved_state", TaskState::Reserved.as_str()))
.bind(("now", SurrealDatetime::from(now))) .bind(("now", SurrealDatetime::from(now)))
.bind(("worker_id", worker_id.to_string())) .bind(("worker_id", worker_id.to_string()))
.bind(("lease_secs", i64::try_from(lease_duration.as_secs()).unwrap_or(i64::MAX))) .bind((
"lease_secs",
i64::try_from(lease_duration.as_secs()).unwrap_or(i64::MAX),
))
.await?; .await?;
let task: Option<IngestionTask> = result.take(0)?; let task: Option<IngestionTask> = result.take(0)?;
@@ -364,7 +365,8 @@ impl IngestionTask {
let now = chrono::Utc::now(); let now = chrono::Utc::now();
let retry_at = now let retry_at = now
.checked_add_signed( .checked_add_signed(
ChronoDuration::from_std(retry_delay).unwrap_or_else(|_| ChronoDuration::seconds(30)), ChronoDuration::from_std(retry_delay)
.unwrap_or_else(|_| ChronoDuration::seconds(30)),
) )
.unwrap_or(now); .unwrap_or(now);
@@ -509,6 +511,7 @@ impl IngestionTask {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use anyhow::{self, Context}; use anyhow::{self, Context};
use super::*; use super::*;
@@ -576,11 +579,13 @@ mod tests {
let user_id = "user123"; let user_id = "user123";
let payload = create_payload(user_id); let payload = create_payload(user_id);
let task = IngestionTask::new(payload, user_id.to_string()); let task = IngestionTask::new(payload, user_id.to_string());
db.store_item(task.clone()).await.with_context(|| "store".to_string())?; db.store_item(task.clone())
.await
.with_context(|| "store".to_string())?;
let worker_id = "worker-1"; let worker_id = "worker-1";
let now = chrono::Utc::now(); let now = chrono::Utc::now();
let claimed = IngestionTask::claim_next_ready(&db, worker_id, now, Duration::from_secs(60)) let claimed = IngestionTask::claim_next_ready(&db, worker_id, now, Duration::from_mins(1))
.await .await
.with_context(|| "claim".to_string())? .with_context(|| "claim".to_string())?
.with_context(|| "task claimed".to_string())?; .with_context(|| "task claimed".to_string())?;
@@ -588,10 +593,16 @@ mod tests {
assert_eq!(claimed.state, TaskState::Reserved); assert_eq!(claimed.state, TaskState::Reserved);
assert_eq!(claimed.worker_id.as_deref(), Some(worker_id)); assert_eq!(claimed.worker_id.as_deref(), Some(worker_id));
let processing = claimed.mark_processing(&db).await.with_context(|| "processing".to_string())?; let processing = claimed
.mark_processing(&db)
.await
.with_context(|| "processing".to_string())?;
assert_eq!(processing.state, TaskState::Processing); assert_eq!(processing.state, TaskState::Processing);
let succeeded = processing.mark_succeeded(&db).await.with_context(|| "succeeded".to_string())?; let succeeded = processing
.mark_succeeded(&db)
.await
.with_context(|| "succeeded".to_string())?;
assert_eq!(succeeded.state, TaskState::Succeeded); assert_eq!(succeeded.state, TaskState::Succeeded);
assert!(succeeded.worker_id.is_none()); assert!(succeeded.worker_id.is_none());
assert!(succeeded.locked_at.is_none()); assert!(succeeded.locked_at.is_none());
@@ -604,16 +615,21 @@ mod tests {
let user_id = "user123"; let user_id = "user123";
let payload = create_payload(user_id); let payload = create_payload(user_id);
let task = IngestionTask::new(payload, user_id.to_string()); let task = IngestionTask::new(payload, user_id.to_string());
db.store_item(task.clone()).await.with_context(|| "store".to_string())?; db.store_item(task.clone())
.await
.with_context(|| "store".to_string())?;
let worker_id = "worker-dead"; let worker_id = "worker-dead";
let now = chrono::Utc::now(); let now = chrono::Utc::now();
let claimed = IngestionTask::claim_next_ready(&db, worker_id, now, Duration::from_secs(60)) let claimed = IngestionTask::claim_next_ready(&db, worker_id, now, Duration::from_mins(1))
.await .await
.with_context(|| "claim".to_string())? .with_context(|| "claim".to_string())?
.with_context(|| "claimed".to_string())?; .with_context(|| "claimed".to_string())?;
let processing = claimed.mark_processing(&db).await.with_context(|| "processing".to_string())?; let processing = claimed
.mark_processing(&db)
.await
.with_context(|| "processing".to_string())?;
let error_info = TaskErrorInfo { let error_info = TaskErrorInfo {
code: Some("pipeline_error".into()), code: Some("pipeline_error".into()),
@@ -646,11 +662,13 @@ mod tests {
let payload = create_payload(user_id); let payload = create_payload(user_id);
let task = IngestionTask::new(payload.clone(), user_id.to_string()); let task = IngestionTask::new(payload.clone(), user_id.to_string());
db.store_item(task.clone()).await.with_context(|| "store".to_string())?; db.store_item(task.clone())
.await
.with_context(|| "store".to_string())?;
let Err(err) = task let Err(err) = task.mark_processing(&db).await else {
.mark_processing(&db) anyhow::bail!("processing should fail without reservation")
.await else { anyhow::bail!("processing should fail without reservation") }; };
match err { match err {
AppError::Validation(message) => { AppError::Validation(message) => {
@@ -671,7 +689,9 @@ mod tests {
let payload = create_payload(user_id); let payload = create_payload(user_id);
let task = IngestionTask::new(payload.clone(), user_id.to_string()); let task = IngestionTask::new(payload.clone(), user_id.to_string());
db.store_item(task.clone()).await.with_context(|| "store".to_string())?; db.store_item(task.clone())
.await
.with_context(|| "store".to_string())?;
let Err(err) = task let Err(err) = task
.mark_failed( .mark_failed(
@@ -682,7 +702,10 @@ mod tests {
Duration::from_secs(30), Duration::from_secs(30),
&db, &db,
) )
.await else { anyhow::bail!("failing should require processing state") }; .await
else {
anyhow::bail!("failing should require processing state")
};
match err { match err {
AppError::Validation(message) => { AppError::Validation(message) => {
@@ -703,11 +726,13 @@ mod tests {
let payload = create_payload(user_id); let payload = create_payload(user_id);
let task = IngestionTask::new(payload.clone(), user_id.to_string()); let task = IngestionTask::new(payload.clone(), user_id.to_string());
db.store_item(task.clone()).await.with_context(|| "store".to_string())?; db.store_item(task.clone())
.await
.with_context(|| "store".to_string())?;
let Err(err) = task let Err(err) = task.release(&db).await else {
.release(&db) anyhow::bail!("release should require reserved state")
.await else { anyhow::bail!("release should require reserved state") }; };
match err { match err {
AppError::Validation(message) => { AppError::Validation(message) => {
+25 -6
View File
@@ -572,9 +572,10 @@ impl KnowledgeEntity {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use anyhow::{self, Context}; #![allow(clippy::expect_used, clippy::must_use_candidate)]
use super::*; use super::*;
use crate::storage::types::knowledge_entity_embedding::KnowledgeEntityEmbedding; use crate::storage::types::knowledge_entity_embedding::KnowledgeEntityEmbedding;
use anyhow::{self, Context};
use serde_json::json; use serde_json::json;
use uuid::Uuid; use uuid::Uuid;
@@ -734,7 +735,10 @@ mod tests {
.with_context(|| "Query failed".to_string())? .with_context(|| "Query failed".to_string())?
.take(0) .take(0)
.with_context(|| "Failed to get query results".to_string())?; .with_context(|| "Failed to get query results".to_string())?;
assert!(remaining.is_empty(), "All entities with the source_id should be deleted"); assert!(
remaining.is_empty(),
"All entities with the source_id should be deleted"
);
let different_query = format!( let different_query = format!(
"SELECT * FROM {} WHERE source_id = '{}'", "SELECT * FROM {} WHERE source_id = '{}'",
@@ -754,7 +758,10 @@ mod tests {
"Entity with different source_id should still exist" "Entity with different source_id should still exist"
); );
assert_eq!( assert_eq!(
different_remaining.first().context("Expected entity to exist")?.id, different_remaining
.first()
.context("Expected entity to exist")?
.id,
different_entity.id different_entity.id
); );
@@ -991,11 +998,19 @@ mod tests {
assert_eq!(results.len(), 2); assert_eq!(results.len(), 2);
assert_eq!( assert_eq!(
results.first().context("Expected at least one result")?.entity.id, results
.first()
.context("Expected at least one result")?
.entity
.id,
e2.id e2.id
); );
assert_eq!( assert_eq!(
results.get(1).context("Expected at least two results")?.entity.id, results
.get(1)
.context("Expected at least two results")?
.entity
.id,
e1.id e1.id
); );
@@ -1042,7 +1057,11 @@ mod tests {
.await .await
.with_context(|| "search should succeed even with orphans".to_string())?; .with_context(|| "search should succeed even with orphans".to_string())?;
assert!(results.is_empty(), "Should return empty result for orphan, got: {:?}", results); assert!(
results.is_empty(),
"Should return empty result for orphan, got: {:?}",
results
);
Ok(()) Ok(())
} }
@@ -137,10 +137,11 @@ impl KnowledgeEntityEmbedding {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use anyhow::{self, Context}; #![allow(clippy::expect_used, clippy::must_use_candidate)]
use super::*; use super::*;
use crate::storage::db::SurrealDbClient; use crate::storage::db::SurrealDbClient;
use crate::storage::types::knowledge_entity::{KnowledgeEntity, KnowledgeEntityType}; use crate::storage::types::knowledge_entity::{KnowledgeEntity, KnowledgeEntityType};
use anyhow::{self, Context};
use chrono::Utc; use chrono::Utc;
use surrealdb::Value as SurrealValue; use surrealdb::Value as SurrealValue;
use uuid::Uuid; use uuid::Uuid;
@@ -270,7 +271,8 @@ mod tests {
let stored_embedding = KnowledgeEntityEmbedding::get_by_entity_id(&entity_rid, &db) let stored_embedding = KnowledgeEntityEmbedding::get_by_entity_id(&entity_rid, &db)
.await .await
.with_context(|| "Failed to fetch embedding".to_string())?; .with_context(|| "Failed to fetch embedding".to_string())?;
let stored_embedding = stored_embedding.ok_or_else(|| anyhow::anyhow!("Expected embedding to exist"))?; let stored_embedding =
stored_embedding.ok_or_else(|| anyhow::anyhow!("Expected embedding to exist"))?;
assert_eq!(stored_embedding.user_id, user_id); assert_eq!(stored_embedding.user_id, user_id);
assert_eq!(stored_embedding.entity_id, entity_rid); assert_eq!(stored_embedding.entity_id, entity_rid);
@@ -399,7 +401,10 @@ mod tests {
.with_context(|| "failed to deserialize fetch rows".to_string())?; .with_context(|| "failed to deserialize fetch rows".to_string())?;
assert_eq!(rows.len(), 1); assert_eq!(rows.len(), 1);
let fetched_entity = &rows.first().context("Expected at least one result")?.entity_id; let fetched_entity = &rows
.first()
.context("Expected at least one result")?
.entity_id;
assert_eq!(fetched_entity.id, entity_key); assert_eq!(fetched_entity.id, entity_key);
assert_eq!(fetched_entity.name, "Test entity"); assert_eq!(fetched_entity.name, "Test entity");
assert_eq!(fetched_entity.user_id, user_id); assert_eq!(fetched_entity.user_id, user_id);
@@ -124,9 +124,10 @@ impl KnowledgeRelationship {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use anyhow::{self, Context}; #![allow(clippy::expect_used, clippy::must_use_candidate)]
use super::*; use super::*;
use crate::storage::types::knowledge_entity::{KnowledgeEntity, KnowledgeEntityType}; use crate::storage::types::knowledge_entity::{KnowledgeEntity, KnowledgeEntityType};
use anyhow::{self, Context};
async fn setup_test_db() -> SurrealDbClient { async fn setup_test_db() -> SurrealDbClient {
let namespace = "test_ns"; let namespace = "test_ns";
@@ -283,8 +284,9 @@ mod tests {
let rows: Vec<KnowledgeRelationship> = res.take(0).expect("take rows"); let rows: Vec<KnowledgeRelationship> = res.take(0).expect("take rows");
assert_eq!(rows.len(), 1); assert_eq!(rows.len(), 1);
let row = rows.first().expect("expected 1 row");
assert_eq!( assert_eq!(
rows[0].metadata.source_id, row.metadata.source_id,
"source123'; DELETE FROM relates_to; --" "source123'; DELETE FROM relates_to; --"
); );
@@ -323,7 +325,10 @@ mod tests {
.with_context(|| "Query failed".to_string())?; .with_context(|| "Query failed".to_string())?;
let before_results: Vec<KnowledgeRelationship> = let before_results: Vec<KnowledgeRelationship> =
existing_before_delete.take(0).unwrap_or_default(); existing_before_delete.take(0).unwrap_or_default();
assert!(!before_results.is_empty(), "Relationship should exist before deletion"); assert!(
!before_results.is_empty(),
"Relationship should exist before deletion"
);
KnowledgeRelationship::delete_relationship_by_id(&relationship.id, &user_id, &db) KnowledgeRelationship::delete_relationship_by_id(&relationship.id, &user_id, &db)
.await .await
@@ -372,7 +377,10 @@ mod tests {
.await .await
.with_context(|| "Query failed".to_string())?; .with_context(|| "Query failed".to_string())?;
let before_results: Vec<KnowledgeRelationship> = before_attempt.take(0).unwrap_or_default(); let before_results: Vec<KnowledgeRelationship> = before_attempt.take(0).unwrap_or_default();
assert!(!before_results.is_empty(), "Relationship should exist before unauthorized delete attempt"); assert!(
!before_results.is_empty(),
"Relationship should exist before unauthorized delete attempt"
);
let result = KnowledgeRelationship::delete_relationship_by_id( let result = KnowledgeRelationship::delete_relationship_by_id(
&relationship.id, &relationship.id,
@@ -383,7 +391,9 @@ mod tests {
match result { match result {
Err(AppError::Auth(_)) => {} Err(AppError::Auth(_)) => {}
_ => anyhow::bail!("Expected authorization error when deleting someone else's relationship"), _ => anyhow::bail!(
"Expected authorization error when deleting someone else's relationship"
),
} }
let mut after_attempt = db let mut after_attempt = db
@@ -394,7 +404,10 @@ mod tests {
.with_context(|| "Query failed".to_string())?; .with_context(|| "Query failed".to_string())?;
let results: Vec<KnowledgeRelationship> = after_attempt.take(0).unwrap_or_default(); let results: Vec<KnowledgeRelationship> = after_attempt.take(0).unwrap_or_default();
assert!(!results.is_empty(), "Relationship should still exist after unauthorized delete attempt"); assert!(
!results.is_empty(),
"Relationship should still exist after unauthorized delete attempt"
);
Ok(()) Ok(())
} }
@@ -484,7 +497,8 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn test_delete_relationships_by_source_id_resists_query_injection() -> anyhow::Result<()> { async fn test_delete_relationships_by_source_id_resists_query_injection() -> anyhow::Result<()>
{
let db = setup_test_db().await; let db = setup_test_db().await;
let entity1_id = create_test_entity("Entity 1", &db).await?; let entity1_id = create_test_entity("Entity 1", &db).await?;
+2 -1
View File
@@ -70,9 +70,10 @@ pub fn format_history(history: &[Message]) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use anyhow::{self, Context}; #![allow(clippy::expect_used, clippy::must_use_candidate)]
use super::*; use super::*;
use crate::storage::db::SurrealDbClient; use crate::storage::db::SurrealDbClient;
use anyhow::{self, Context};
#[tokio::test] #[tokio::test]
async fn test_message_creation() -> anyhow::Result<()> { async fn test_message_creation() -> anyhow::Result<()> {
+1 -1
View File
@@ -1,6 +1,5 @@
#![allow(clippy::unsafe_derive_deserialize)] #![allow(clippy::unsafe_derive_deserialize)]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod serde_helpers;
pub mod analytics; pub mod analytics;
pub mod conversation; pub mod conversation;
pub mod file_info; pub mod file_info;
@@ -11,6 +10,7 @@ pub mod knowledge_entity_embedding;
pub mod knowledge_relationship; pub mod knowledge_relationship;
pub mod message; pub mod message;
pub mod scratchpad; pub mod scratchpad;
pub mod serde_helpers;
pub mod system_prompts; pub mod system_prompts;
pub mod system_settings; pub mod system_settings;
pub mod text_chunk; pub mod text_chunk;
+47 -13
View File
@@ -216,6 +216,7 @@ impl Scratchpad {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use anyhow::{self, Context}; use anyhow::{self, Context};
use super::*; use super::*;
@@ -289,9 +290,15 @@ mod tests {
// Store them // Store them
let scratchpad1_id = scratchpad1.id.clone(); let scratchpad1_id = scratchpad1.id.clone();
let scratchpad2_id = scratchpad2.id.clone(); let scratchpad2_id = scratchpad2.id.clone();
db.store_item(scratchpad1).await.with_context(|| "store scratchpad1".to_string())?; db.store_item(scratchpad1)
db.store_item(scratchpad2).await.with_context(|| "store scratchpad2".to_string())?; .await
db.store_item(scratchpad3).await.with_context(|| "store scratchpad3".to_string())?; .with_context(|| "store scratchpad1".to_string())?;
db.store_item(scratchpad2)
.await
.with_context(|| "store scratchpad2".to_string())?;
db.store_item(scratchpad3)
.await
.with_context(|| "store scratchpad3".to_string())?;
// Archive one of the user's scratchpads // Archive one of the user's scratchpads
Scratchpad::archive(&scratchpad2_id, user_id, &db, false) Scratchpad::archive(&scratchpad2_id, user_id, &db, false)
@@ -303,7 +310,10 @@ mod tests {
.await .await
.with_context(|| "get_by_user".to_string())?; .with_context(|| "get_by_user".to_string())?;
assert_eq!(user_scratchpads.len(), 1); assert_eq!(user_scratchpads.len(), 1);
assert_eq!(user_scratchpads.first().map(|s| &s.id), Some(&scratchpad1_id)); assert_eq!(
user_scratchpads.first().map(|s| &s.id),
Some(&scratchpad1_id)
);
// Verify they belong to the user // Verify they belong to the user
for scratchpad in &user_scratchpads { for scratchpad in &user_scratchpads {
@@ -335,7 +345,9 @@ mod tests {
let user_id = "test_user"; let user_id = "test_user";
let scratchpad = Scratchpad::new(user_id.to_string(), "Test".to_string()); let scratchpad = Scratchpad::new(user_id.to_string(), "Test".to_string());
let scratchpad_id = scratchpad.id.clone(); let scratchpad_id = scratchpad.id.clone();
db.store_item(scratchpad).await.with_context(|| "store scratchpad".to_string())?; db.store_item(scratchpad)
.await
.with_context(|| "store scratchpad".to_string())?;
let archived = Scratchpad::archive(&scratchpad_id, user_id, &db, true) let archived = Scratchpad::archive(&scratchpad_id, user_id, &db, true)
.await .await
@@ -369,7 +381,9 @@ mod tests {
let scratchpad = Scratchpad::new(user_id.to_string(), "Test".to_string()); let scratchpad = Scratchpad::new(user_id.to_string(), "Test".to_string());
let scratchpad_id = scratchpad.id.clone(); let scratchpad_id = scratchpad.id.clone();
db.store_item(scratchpad).await.with_context(|| "store scratchpad".to_string())?; db.store_item(scratchpad)
.await
.with_context(|| "store scratchpad".to_string())?;
let new_content = "Updated content"; let new_content = "Updated content";
let updated = Scratchpad::update_content(&scratchpad_id, user_id, new_content, &db) let updated = Scratchpad::update_content(&scratchpad_id, user_id, new_content, &db)
@@ -398,7 +412,9 @@ mod tests {
let scratchpad = Scratchpad::new(owner_id.to_string(), "Test".to_string()); let scratchpad = Scratchpad::new(owner_id.to_string(), "Test".to_string());
let scratchpad_id = scratchpad.id.clone(); let scratchpad_id = scratchpad.id.clone();
db.store_item(scratchpad).await.with_context(|| "store scratchpad".to_string())?; db.store_item(scratchpad)
.await
.with_context(|| "store scratchpad".to_string())?;
let result = Scratchpad::update_content(&scratchpad_id, other_user, "Hacked", &db).await; let result = Scratchpad::update_content(&scratchpad_id, other_user, "Hacked", &db).await;
assert!(result.is_err()); assert!(result.is_err());
@@ -425,14 +441,19 @@ mod tests {
let scratchpad = Scratchpad::new(user_id.to_string(), "Test".to_string()); let scratchpad = Scratchpad::new(user_id.to_string(), "Test".to_string());
let scratchpad_id = scratchpad.id.clone(); let scratchpad_id = scratchpad.id.clone();
db.store_item(scratchpad).await.with_context(|| "store scratchpad".to_string())?; db.store_item(scratchpad)
.await
.with_context(|| "store scratchpad".to_string())?;
// Delete should succeed // Delete should succeed
let result = Scratchpad::delete(&scratchpad_id, user_id, &db).await; let result = Scratchpad::delete(&scratchpad_id, user_id, &db).await;
assert!(result.is_ok()); assert!(result.is_ok());
// Verify it's gone // Verify it's gone
let retrieved: Option<Scratchpad> = db.get_item(&scratchpad_id).await.with_context(|| "get_item".to_string())?; let retrieved: Option<Scratchpad> = db
.get_item(&scratchpad_id)
.await
.with_context(|| "get_item".to_string())?;
assert!(retrieved.is_none()); assert!(retrieved.is_none());
Ok(()) Ok(())
} }
@@ -454,7 +475,9 @@ mod tests {
let scratchpad = Scratchpad::new(owner_id.to_string(), "Test".to_string()); let scratchpad = Scratchpad::new(owner_id.to_string(), "Test".to_string());
let scratchpad_id = scratchpad.id.clone(); let scratchpad_id = scratchpad.id.clone();
db.store_item(scratchpad).await.with_context(|| "store scratchpad".to_string())?; db.store_item(scratchpad)
.await
.with_context(|| "store scratchpad".to_string())?;
let result = Scratchpad::delete(&scratchpad_id, other_user, &db).await; let result = Scratchpad::delete(&scratchpad_id, other_user, &db).await;
assert!(result.is_err()); assert!(result.is_err());
@@ -464,7 +487,10 @@ mod tests {
} }
// Verify it still exists // Verify it still exists
let retrieved: Option<Scratchpad> = db.get_item(&scratchpad_id).await.with_context(|| "get_item".to_string())?; let retrieved: Option<Scratchpad> = db
.get_item(&scratchpad_id)
.await
.with_context(|| "get_item".to_string())?;
assert!(retrieved.is_some()); assert!(retrieved.is_some());
Ok(()) Ok(())
} }
@@ -484,7 +510,9 @@ mod tests {
Scratchpad::new(user_id.to_string(), "Test Timezone Scratchpad".to_string()); Scratchpad::new(user_id.to_string(), "Test Timezone Scratchpad".to_string());
let scratchpad_id = scratchpad.id.clone(); let scratchpad_id = scratchpad.id.clone();
db.store_item(scratchpad).await.with_context(|| "store scratchpad".to_string())?; db.store_item(scratchpad)
.await
.with_context(|| "store scratchpad".to_string())?;
let retrieved = Scratchpad::get_by_id(&scratchpad_id, user_id, &db) let retrieved = Scratchpad::get_by_id(&scratchpad_id, user_id, &db)
.await .await
@@ -505,7 +533,13 @@ mod tests {
.with_context(|| "archive".to_string())?; .with_context(|| "archive".to_string())?;
assert!(archived.archived_at.is_some()); assert!(archived.archived_at.is_some());
assert!(archived.archived_at.with_context(|| "expected archived_at".to_string())?.timestamp() > 0); assert!(
archived
.archived_at
.with_context(|| "expected archived_at".to_string())?
.timestamp()
> 0
);
assert!(archived.ingested_at.is_none()); assert!(archived.ingested_at.is_none());
Ok(()) Ok(())
} }
+3 -1
View File
@@ -71,7 +71,9 @@ where
} }
} }
pub fn deserialize_option_datetime<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error> pub fn deserialize_option_datetime<'de, D>(
deserializer: D,
) -> Result<Option<DateTime<Utc>>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
+10 -7
View File
@@ -64,8 +64,7 @@ impl SystemSettings {
let mut needs_update = false; let mut needs_update = false;
let backend_label = provider.backend_label().to_string(); let backend_label = provider.backend_label().to_string();
let provider_dimensions = u32::try_from(provider.dimension()) let provider_dimensions = u32::try_from(provider.dimension()).unwrap_or_else(|_| {
.unwrap_or_else(|_| {
tracing::warn!( tracing::warn!(
"Provider dimension {} exceeds u32 max; falling back to 0", "Provider dimension {} exceeds u32 max; falling back to 0",
provider.dimension() provider.dimension()
@@ -114,9 +113,10 @@ impl SystemSettings {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use anyhow::{self, Context}; #![allow(clippy::expect_used, clippy::must_use_candidate)]
use crate::storage::indexes::ensure_runtime; use crate::storage::indexes::ensure_runtime;
use crate::storage::types::{knowledge_entity::KnowledgeEntity, text_chunk::TextChunk}; use crate::storage::types::{knowledge_entity::KnowledgeEntity, text_chunk::TextChunk};
use anyhow::{self, Context};
use async_openai::Client; use async_openai::Client;
use super::*; use super::*;
@@ -138,8 +138,8 @@ mod tests {
.take(0) .take(0)
.with_context(|| "Failed to extract table info response".to_string())?; .with_context(|| "Failed to extract table info response".to_string())?;
let info_json: serde_json::Value = let info_json: serde_json::Value = serde_json::to_value(info)
serde_json::to_value(info).with_context(|| "Failed to convert info to json".to_string())?; .with_context(|| "Failed to convert info to json".to_string())?;
let indexes = info_json let indexes = info_json
.get("Object") .get("Object")
@@ -383,7 +383,8 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn test_should_change_embedding_length_on_indexes_when_switching_length() -> anyhow::Result<()> { async fn test_should_change_embedding_length_on_indexes_when_switching_length(
) -> anyhow::Result<()> {
let db = SurrealDbClient::memory("test", &Uuid::new_v4().to_string()) let db = SurrealDbClient::memory("test", &Uuid::new_v4().to_string())
.await .await
.with_context(|| "Failed to start DB".to_string())?; .with_context(|| "Failed to start DB".to_string())?;
@@ -436,7 +437,9 @@ mod tests {
.with_context(|| "TextChunk re-embedding should succeed on fresh DB".to_string())?; .with_context(|| "TextChunk re-embedding should succeed on fresh DB".to_string())?;
KnowledgeEntity::update_all_embeddings(&db, &openai_client, &new_model, new_dimension) KnowledgeEntity::update_all_embeddings(&db, &openai_client, &new_model, new_dimension)
.await .await
.with_context(|| "KnowledgeEntity re-embedding should succeed on fresh DB".to_string())?; .with_context(|| {
"KnowledgeEntity re-embedding should succeed on fresh DB".to_string()
})?;
let text_chunk_dimension = get_hnsw_index_dimension( let text_chunk_dimension = get_hnsw_index_dimension(
&db, &db,
+52 -25
View File
@@ -237,7 +237,9 @@ impl TextChunk {
new_model: &str, new_model: &str,
new_dimensions: u32, new_dimensions: u32,
) -> Result<(), AppError> { ) -> Result<(), AppError> {
info!("Starting re-embedding process for all text chunks. New dimensions: {new_dimensions}"); info!(
"Starting re-embedding process for all text chunks. New dimensions: {new_dimensions}"
);
// Fetch all chunks first // Fetch all chunks first
let all_chunks: Vec<TextChunk> = db.select(Self::table_name()).await?; let all_chunks: Vec<TextChunk> = db.select(Self::table_name()).await?;
@@ -451,6 +453,7 @@ impl TextChunk {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use anyhow::{self, Context}; use anyhow::{self, Context};
use super::*; use super::*;
@@ -501,7 +504,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
let source_id = "source123".to_string(); let source_id = "source123".to_string();
let user_id = "user123".to_string(); let user_id = "user123".to_string();
@@ -580,7 +585,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
TextChunkEmbedding::redefine_hnsw_index(&db, 5) TextChunkEmbedding::redefine_hnsw_index(&db, 5)
.await .await
.with_context(|| "redefine index".to_string())?; .with_context(|| "redefine index".to_string())?;
@@ -672,7 +679,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
let source_id = "store-src".to_string(); let source_id = "store-src".to_string();
let user_id = "user_store".to_string(); let user_id = "user_store".to_string();
@@ -686,7 +695,8 @@ mod tests {
.await .await
.with_context(|| "store with embedding".to_string())?; .with_context(|| "store with embedding".to_string())?;
let stored_chunk: Option<TextChunk> = db.get_item(&chunk.id) let stored_chunk: Option<TextChunk> = db
.get_item(&chunk.id)
.await .await
.with_context(|| "get_item".to_string())?; .with_context(|| "get_item".to_string())?;
let stored_chunk = stored_chunk.with_context(|| "expected stored chunk".to_string())?; let stored_chunk = stored_chunk.with_context(|| "expected stored chunk".to_string())?;
@@ -711,7 +721,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
let embedding_dimension = 3usize; let embedding_dimension = 3usize;
ensure_runtime(&db, embedding_dimension) ensure_runtime(&db, embedding_dimension)
@@ -728,7 +740,8 @@ mod tests {
.await .await
.with_context(|| "store with embedding".to_string())?; .with_context(|| "store with embedding".to_string())?;
let stored_chunk: Option<TextChunk> = db.get_item(&chunk.id) let stored_chunk: Option<TextChunk> = db
.get_item(&chunk.id)
.await .await
.with_context(|| "get_item".to_string())?; .with_context(|| "get_item".to_string())?;
let stored_chunk = stored_chunk.with_context(|| "chunk should be stored".to_string())?; let stored_chunk = stored_chunk.with_context(|| "chunk should be stored".to_string())?;
@@ -754,7 +767,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
TextChunkEmbedding::redefine_hnsw_index(&db, 3) TextChunkEmbedding::redefine_hnsw_index(&db, 3)
.await .await
@@ -775,7 +790,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
TextChunkEmbedding::redefine_hnsw_index(&db, 3) TextChunkEmbedding::redefine_hnsw_index(&db, 3)
.await .await
@@ -813,7 +830,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
TextChunkEmbedding::redefine_hnsw_index(&db, 3) TextChunkEmbedding::redefine_hnsw_index(&db, 3)
.await .await
@@ -836,14 +855,8 @@ mod tests {
.with_context(|| "vector_search".to_string())?; .with_context(|| "vector_search".to_string())?;
assert_eq!(results.len(), 2); assert_eq!(results.len(), 2);
assert_eq!( assert_eq!(results.first().map(|r| &r.chunk.id), Some(&chunk2.id));
results.first().map(|r| &r.chunk.id), assert_eq!(results.get(1).map(|r| &r.chunk.id), Some(&chunk1.id));
Some(&chunk2.id)
);
assert_eq!(
results.get(1).map(|r| &r.chunk.id),
Some(&chunk1.id)
);
let r0 = results.first().context("expected first result")?; let r0 = results.first().context("expected first result")?;
let r1 = results.get(1).context("expected second result")?; let r1 = results.get(1).context("expected second result")?;
assert!(r0.score >= r1.score); assert!(r0.score >= r1.score);
@@ -857,9 +870,13 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
ensure_chunk_fts_index(&db).await?; ensure_chunk_fts_index(&db).await?;
rebuild(&db).await.with_context(|| "rebuild indexes".to_string())?; rebuild(&db)
.await
.with_context(|| "rebuild indexes".to_string())?;
let results = TextChunk::fts_search(5, "hello", &db, "user") let results = TextChunk::fts_search(5, "hello", &db, "user")
.await .await
@@ -876,7 +893,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
ensure_chunk_fts_index(&db).await?; ensure_chunk_fts_index(&db).await?;
let user_id = "fts_user"; let user_id = "fts_user";
@@ -885,8 +904,12 @@ mod tests {
"rustaceans love rust".to_string(), "rustaceans love rust".to_string(),
user_id.to_string(), user_id.to_string(),
); );
db.store_item(chunk.clone()).await.with_context(|| "store chunk".to_string())?; db.store_item(chunk.clone())
rebuild(&db).await.with_context(|| "rebuild indexes".to_string())?; .await
.with_context(|| "store chunk".to_string())?;
rebuild(&db)
.await
.with_context(|| "rebuild indexes".to_string())?;
let results = TextChunk::fts_search(3, "rust", &db, user_id) let results = TextChunk::fts_search(3, "rust", &db, user_id)
.await .await
@@ -906,7 +929,9 @@ mod tests {
let db = SurrealDbClient::memory(namespace, database) let db = SurrealDbClient::memory(namespace, database)
.await .await
.with_context(|| "Failed to start in-memory surrealdb".to_string())?; .with_context(|| "Failed to start in-memory surrealdb".to_string())?;
db.apply_migrations().await.with_context(|| "migrations".to_string())?; db.apply_migrations()
.await
.with_context(|| "migrations".to_string())?;
ensure_chunk_fts_index(&db).await?; ensure_chunk_fts_index(&db).await?;
let user_id = "fts_user_order"; let user_id = "fts_user_order";
@@ -935,7 +960,9 @@ mod tests {
db.store_item(other_user_chunk) db.store_item(other_user_chunk)
.await .await
.with_context(|| "store other user chunk".to_string())?; .with_context(|| "store other user chunk".to_string())?;
rebuild(&db).await.with_context(|| "rebuild indexes".to_string())?; rebuild(&db)
.await
.with_context(|| "rebuild indexes".to_string())?;
let results = TextChunk::fts_search(3, "apple", &db, user_id) let results = TextChunk::fts_search(3, "apple", &db, user_id)
.await .await
@@ -126,6 +126,7 @@ impl TextChunkEmbedding {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use anyhow::{self, Context}; use anyhow::{self, Context};
use super::*; use super::*;
@@ -177,9 +178,11 @@ mod tests {
.query("INFO FOR TABLE text_chunk_embedding;") .query("INFO FOR TABLE text_chunk_embedding;")
.await .await
.with_context(|| "info query failed".to_string())?; .with_context(|| "info query failed".to_string())?;
let info: SurrealValue = info_res.take(0).with_context(|| "failed to take info result".to_string())?; let info: SurrealValue = info_res
let info_json: serde_json::Value = .take(0)
serde_json::to_value(info).with_context(|| "failed to convert info to json".to_string())?; .with_context(|| "failed to take info result".to_string())?;
let info_json: serde_json::Value = serde_json::to_value(info)
.with_context(|| "failed to convert info to json".to_string())?;
let idx_sql = info_json let idx_sql = info_json
.get("Object") .get("Object")
.and_then(|v| v.get("indexes")) .and_then(|v| v.get("indexes"))
+1
View File
@@ -185,6 +185,7 @@ impl TextContent {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use anyhow::{self, Context}; use anyhow::{self, Context};
use super::*; use super::*;
+14 -4
View File
@@ -723,6 +723,7 @@ impl User {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use anyhow::{self, Context}; use anyhow::{self, Context};
use super::*; use super::*;
@@ -908,12 +909,16 @@ mod tests {
first.created_at -= chrono::Duration::minutes(1); first.created_at -= chrono::Duration::minutes(1);
first.updated_at = first.created_at; first.updated_at = first.created_at;
first.state = TaskState::Succeeded; first.state = TaskState::Succeeded;
db.store_item(first.clone()).await.with_context(|| "store first".to_string())?; db.store_item(first.clone())
.await
.with_context(|| "store first".to_string())?;
// Latest task // Latest task
let mut second = IngestionTask::new(payload.clone(), user_id.to_string()); let mut second = IngestionTask::new(payload.clone(), user_id.to_string());
second.state = TaskState::Processing; second.state = TaskState::Processing;
db.store_item(second.clone()).await.with_context(|| "store second".to_string())?; db.store_item(second.clone())
.await
.with_context(|| "store second".to_string())?;
let other_payload = IngestionPayload::Text { let other_payload = IngestionPayload::Text {
text: "Other".to_string(), text: "Other".to_string(),
@@ -922,7 +927,9 @@ mod tests {
user_id: other_user_id.to_string(), user_id: other_user_id.to_string(),
}; };
let other_task = IngestionTask::new(other_payload, other_user_id.to_string()); let other_task = IngestionTask::new(other_payload, other_user_id.to_string());
db.store_item(other_task).await.with_context(|| "store other".to_string())?; db.store_item(other_task)
.await
.with_context(|| "store other".to_string())?;
let tasks = User::get_all_ingestion_tasks(user_id, &db) let tasks = User::get_all_ingestion_tasks(user_id, &db)
.await .await
@@ -1192,7 +1199,10 @@ mod tests {
} }
// Check first conversation title matches the most recently updated // Check first conversation title matches the most recently updated
let most_recent = conversations.iter().max_by_key(|c| c.created_at).context("expected most recent")?; let most_recent = conversations
.iter()
.max_by_key(|c| c.created_at)
.context("expected most recent")?;
let r0 = retrieved.first().context("expected first result")?; let r0 = retrieved.first().context("expected first result")?;
assert_eq!(r0.id, most_recent.id); assert_eq!(r0.id, most_recent.id);
Ok(()) Ok(())
+3 -2
View File
@@ -9,7 +9,7 @@ pub enum IngestValidationError {
pub fn validate_ingest_input( pub fn validate_ingest_input(
config: &AppConfig, config: &AppConfig,
content: Option<&str>, content: Option<&str>,
context: &str, ctx: &str,
category: &str, category: &str,
file_count: usize, file_count: usize,
) -> Result<(), IngestValidationError> { ) -> Result<(), IngestValidationError> {
@@ -29,7 +29,7 @@ pub fn validate_ingest_input(
} }
} }
if context.len() > config.ingest_max_context_bytes { if ctx.len() > config.ingest_max_context_bytes {
return Err(IngestValidationError::PayloadTooLarge(format!( return Err(IngestValidationError::PayloadTooLarge(format!(
"Context is too large. Maximum allowed is {} bytes", "Context is too large. Maximum allowed is {} bytes",
config.ingest_max_context_bytes config.ingest_max_context_bytes
@@ -48,6 +48,7 @@ pub fn validate_ingest_input(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::must_use_candidate)]
use super::*; use super::*;
#[test] #[test]