mirror of
https://github.com/perstarkse/minne.git
synced 2026-05-28 02:19:34 +02:00
chore: index slicing and lowercase errors
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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())?);
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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."));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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?;
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|||||||
@@ -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::*;
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user