#![cfg(all(test, feature = "persistence"))]
use crate::collection::types::CollectionConfig;
use crate::distance::DistanceMetric;
use crate::index::hnsw::HnswParams;
use crate::quantization::StorageMode;
use crate::Collection;
use std::path::PathBuf;
#[test]
fn test_hnsw_params_persisted_in_config_json() {
let temp_dir = tempfile::tempdir().expect("temp dir should be created");
let params = HnswParams::custom(64, 400, 50_000);
let collection = Collection::create_with_hnsw_params(
PathBuf::from(temp_dir.path()),
128,
DistanceMetric::Cosine,
StorageMode::Full,
params,
)
.expect("collection should be created");
let cfg = collection.config();
assert_eq!(cfg.hnsw_params, Some(params));
let config_path = temp_dir.path().join("config.json");
let raw = std::fs::read_to_string(&config_path).expect("config.json should exist");
let deserialized: CollectionConfig =
serde_json::from_str(&raw).expect("config.json should deserialize");
assert_eq!(deserialized.hnsw_params, Some(params));
}
#[test]
fn test_config_without_hnsw_params_loads_as_none() {
let json = r#"{
"name": "legacy",
"dimension": 128,
"metric": "Cosine",
"point_count": 0,
"storage_mode": "full",
"metadata_only": false
}"#;
let cfg: CollectionConfig =
serde_json::from_str(json).expect("legacy config should deserialize");
assert!(cfg.hnsw_params.is_none());
}
#[test]
fn test_reopen_collection_uses_persisted_hnsw_params() {
let temp_dir = tempfile::tempdir().expect("temp dir should be created");
let params = HnswParams::custom(64, 400, 50_000);
let _collection = Collection::create_with_hnsw_params(
PathBuf::from(temp_dir.path()),
128,
DistanceMetric::Cosine,
StorageMode::Full,
params,
)
.expect("collection should be created");
assert!(
!temp_dir.path().join("hnsw.bin").exists(),
"hnsw.bin should not exist for empty collection"
);
let reopened =
Collection::open(PathBuf::from(temp_dir.path())).expect("collection should reopen");
let cfg = reopened.config();
assert_eq!(
cfg.hnsw_params,
Some(params),
"reopened collection should preserve custom HNSW params"
);
}
#[test]
fn test_default_collection_omits_hnsw_params_from_json() {
let temp_dir = tempfile::tempdir().expect("temp dir should be created");
let _collection =
Collection::create(PathBuf::from(temp_dir.path()), 128, DistanceMetric::Cosine)
.expect("collection should be created");
let config_path = temp_dir.path().join("config.json");
let raw = std::fs::read_to_string(&config_path).expect("config.json should exist");
assert!(
!raw.contains("hnsw_params"),
"config.json should not contain hnsw_params when None"
);
}
fn expect_err(result: crate::error::Result<Collection>) -> crate::Error {
match result {
Err(e) => e,
Ok(_) => panic!("expected Err, got Ok"),
}
}
#[test]
fn test_create_rejects_zero_dimension() {
let temp_dir = tempfile::tempdir().expect("temp dir should be created");
let result = Collection::create(PathBuf::from(temp_dir.path()), 0, DistanceMetric::Cosine);
let err = expect_err(result);
assert_eq!(err.code(), "VELES-032");
}
#[test]
fn test_create_rejects_oversized_dimension() {
let temp_dir = tempfile::tempdir().expect("temp dir should be created");
let result = Collection::create(
PathBuf::from(temp_dir.path()),
100_000,
DistanceMetric::Cosine,
);
let err = expect_err(result);
assert_eq!(err.code(), "VELES-032");
}
#[test]
fn test_create_accepts_min_dimension() {
let temp_dir = tempfile::tempdir().expect("temp dir should be created");
let result = Collection::create(PathBuf::from(temp_dir.path()), 1, DistanceMetric::Cosine);
assert!(result.is_ok(), "dimension 1 should be accepted");
}
#[test]
fn test_create_accepts_max_dimension() {
use crate::validation::validate_dimension;
assert!(
validate_dimension(65_536).is_ok(),
"dimension 65_536 should pass validation"
);
assert!(
validate_dimension(65_537).is_err(),
"dimension 65_537 should be rejected"
);
}
#[test]
fn test_create_with_hnsw_params_rejects_zero_dimension() {
let temp_dir = tempfile::tempdir().expect("temp dir should be created");
let params = HnswParams::custom(16, 200, 10_000);
let result = Collection::create_with_hnsw_params(
PathBuf::from(temp_dir.path()),
0,
DistanceMetric::Cosine,
StorageMode::Full,
params,
);
let err = expect_err(result);
assert_eq!(err.code(), "VELES-032");
}
#[test]
fn test_graph_collection_rejects_zero_embedding_dim() {
use crate::collection::graph::GraphSchema;
let temp_dir = tempfile::tempdir().expect("temp dir should be created");
let schema = GraphSchema::new();
let result = Collection::create_graph_collection(
PathBuf::from(temp_dir.path()),
"test_graph",
schema,
Some(0),
DistanceMetric::Cosine,
);
let err = expect_err(result);
assert_eq!(err.code(), "VELES-032");
}
#[test]
fn test_graph_collection_accepts_none_embedding_dim() {
use crate::collection::graph::GraphSchema;
let temp_dir = tempfile::tempdir().expect("temp dir should be created");
let schema = GraphSchema::new();
let result = Collection::create_graph_collection(
PathBuf::from(temp_dir.path()),
"test_graph",
schema,
None,
DistanceMetric::Cosine,
);
assert!(
result.is_ok(),
"embedding_dim None should be accepted for graph collections"
);
}
#[test]
fn test_reopen_collection_reconciles_point_count_from_storage() {
use crate::point::Point;
let temp_dir = tempfile::tempdir().expect("temp dir should be created");
let n = 25_usize;
let collection = Collection::create(PathBuf::from(temp_dir.path()), 4, DistanceMetric::Cosine)
.expect("collection should be created");
#[allow(clippy::cast_precision_loss)]
let points: Vec<Point> = (0..n)
.map(|i| {
let f = i as f32 / n as f32;
Point::without_payload(i as u64, vec![f, 1.0 - f, 0.5, 0.1])
})
.collect();
collection.upsert(points).expect("upsert should succeed");
assert_eq!(collection.len(), n, "in-memory len should equal N");
drop(collection);
let reopened =
Collection::open(PathBuf::from(temp_dir.path())).expect("collection should reopen");
assert_eq!(
reopened.config().point_count,
n,
"config.point_count must be reconciled from storage on open"
);
assert_eq!(
reopened.len(),
n,
"len() must reflect actual vector count after reopen"
);
}