#[cfg(test)]
mod tests {
use crate::config::*;
#[test]
fn test_search_mode_ef_search_values() {
assert_eq!(SearchMode::Fast.ef_search(), 96);
assert_eq!(SearchMode::Balanced.ef_search(), 160);
assert_eq!(SearchMode::Accurate.ef_search(), 512);
assert_eq!(SearchMode::Perfect.ef_search(), usize::MAX);
}
#[test]
fn test_search_mode_default_is_balanced() {
let mode = SearchMode::default();
assert_eq!(mode, SearchMode::Balanced);
}
#[test]
fn test_search_mode_serialization() {
let mode = SearchMode::Accurate;
let json = serde_json::to_string(&mode).expect("serialize");
let deserialized: SearchMode = serde_json::from_str(&json).expect("deserialize");
assert_eq!(json, "\"accurate\"");
assert_eq!(deserialized, mode);
}
#[test]
fn test_config_default_values() {
let config = VelesConfig::default();
assert_eq!(config.search.default_mode, SearchMode::Balanced);
assert_eq!(config.search.max_results, 1000);
assert_eq!(config.search.query_timeout_ms, 30000);
assert!(config.search.ef_search.is_none());
assert_eq!(config.server.port, 8080);
assert_eq!(config.storage.storage_mode, "mmap");
assert_eq!(config.logging.level, "info");
}
#[test]
fn test_config_effective_ef_search_from_mode() {
let config = VelesConfig::default();
let ef = config.effective_ef_search();
assert_eq!(ef, 160); }
#[test]
fn test_config_effective_ef_search_override() {
let mut config = VelesConfig::default();
config.search.ef_search = Some(512);
let ef = config.effective_ef_search();
assert_eq!(ef, 512);
}
#[test]
fn test_config_from_toml_minimal() {
let toml = r#"
[search]
default_mode = "fast"
"#;
let config = VelesConfig::from_toml(toml).expect("parse");
assert_eq!(config.search.default_mode, SearchMode::Fast);
assert_eq!(config.server.port, 8080);
}
#[test]
fn test_config_from_toml_full() {
let toml = r#"
[search]
default_mode = "accurate"
ef_search = 512
max_results = 500
query_timeout_ms = 60000
[hnsw]
m = 48
ef_construction = 600
[storage]
data_dir = "/var/lib/velesdb"
storage_mode = "mmap"
mmap_cache_mb = 2048
[limits]
max_dimensions = 2048
max_perfect_mode_vectors = 100000
[server]
host = "0.0.0.0"
port = 9090
workers = 8
[logging]
level = "debug"
format = "json"
"#;
let config = VelesConfig::from_toml(toml).expect("parse");
assert_eq!(config.search.default_mode, SearchMode::Accurate);
assert_eq!(config.search.ef_search, Some(512));
assert_eq!(config.search.max_results, 500);
assert_eq!(config.hnsw.m, Some(48));
assert_eq!(config.hnsw.ef_construction, Some(600));
assert_eq!(config.storage.data_dir, "/var/lib/velesdb");
assert_eq!(config.storage.mmap_cache_mb, 2048);
assert_eq!(config.limits.max_dimensions, 2048);
assert_eq!(config.server.host, "0.0.0.0");
assert_eq!(config.server.port, 9090);
assert_eq!(config.server.workers, 8);
assert_eq!(config.logging.level, "debug");
assert_eq!(config.logging.format, "json");
}
#[test]
fn test_config_from_toml_invalid_mode() {
let toml = r#"
[search]
default_mode = "ultra_fast"
"#;
let result = VelesConfig::from_toml(toml);
assert!(result.is_err());
}
#[test]
fn test_config_validate_success() {
let config = VelesConfig::default();
let result = config.validate();
assert!(result.is_ok());
}
#[test]
fn test_config_validate_ef_search_too_low() {
let mut config = VelesConfig::default();
config.search.ef_search = Some(10);
let result = config.validate();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("search.ef_search"));
}
#[test]
fn test_config_validate_ef_search_too_high() {
let mut config = VelesConfig::default();
config.search.ef_search = Some(5000);
let result = config.validate();
assert!(result.is_err());
}
#[test]
fn test_config_validate_invalid_storage_mode() {
let mut config = VelesConfig::default();
config.storage.storage_mode = "disk".to_string();
let result = config.validate();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("storage.storage_mode"));
}
#[test]
fn test_config_validate_invalid_log_level() {
let mut config = VelesConfig::default();
config.logging.level = "verbose".to_string();
let result = config.validate();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("logging.level"));
}
#[test]
fn test_config_validate_port_too_low() {
let mut config = VelesConfig::default();
config.server.port = 80;
let result = config.validate();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("server.port"));
}
#[test]
fn test_config_validate_hnsw_m_out_of_range() {
let mut config = VelesConfig::default();
config.hnsw.m = Some(2);
let result = config.validate();
assert!(result.is_err());
}
#[test]
fn test_config_to_toml() {
let config = VelesConfig::default();
let toml_str = config.to_toml().expect("serialize");
assert!(toml_str.contains("[search]"));
assert!(toml_str.contains("default_mode"));
assert!(toml_str.contains("[server]"));
assert!(toml_str.contains("port = 8080"));
}
#[test]
fn test_config_roundtrip() {
let mut config = VelesConfig::default();
config.search.default_mode = SearchMode::Accurate;
config.search.ef_search = Some(300);
config.server.port = 9000;
let toml_str = config.to_toml().expect("serialize");
let parsed = VelesConfig::from_toml(&toml_str).expect("parse");
assert_eq!(parsed.search.default_mode, SearchMode::Accurate);
assert_eq!(parsed.search.ef_search, Some(300));
assert_eq!(parsed.server.port, 9000);
}
#[test]
fn test_quantization_type_old_format_sq8() {
let json = r#"{
"default_type": "sq8",
"rerank_enabled": true,
"rerank_multiplier": 2,
"auto_quantization": true,
"auto_quantization_threshold": 10000
}"#;
let config: QuantizationConfig = serde_json::from_str(json).expect("deserialize old sq8");
assert_eq!(*config.mode(), QuantizationType::SQ8);
assert!(config.rerank_enabled);
assert_eq!(config.rerank_multiplier, 2);
}
#[test]
fn test_quantization_type_old_format_none() {
let json = r#"{"default_type": "none"}"#;
let config: QuantizationConfig = serde_json::from_str(json).expect("deserialize old none");
assert_eq!(*config.mode(), QuantizationType::None);
}
#[test]
fn test_quantization_type_old_format_binary() {
let json = r#"{"default_type": "binary"}"#;
let config: QuantizationConfig =
serde_json::from_str(json).expect("deserialize old binary");
assert_eq!(*config.mode(), QuantizationType::Binary);
}
#[test]
fn test_quantization_type_new_format_pq() {
let json = r#"{
"mode": {"type": "pq", "m": 8, "k": 256, "opq_enabled": false, "oversampling": 4}
}"#;
let config: QuantizationConfig = serde_json::from_str(json).expect("deserialize new pq");
match config.mode() {
QuantizationType::PQ {
m,
k,
opq_enabled,
oversampling,
} => {
assert_eq!(*m, 8);
assert_eq!(*k, 256);
assert!(!opq_enabled);
assert_eq!(*oversampling, Some(4));
}
other => panic!("Expected PQ, got {other:?}"),
}
}
#[test]
fn test_quantization_type_new_format_rabitq() {
let json = r#"{"mode": {"type": "rabitq"}}"#;
let config: QuantizationConfig =
serde_json::from_str(json).expect("deserialize new rabitq");
assert_eq!(*config.mode(), QuantizationType::RaBitQ);
}
#[test]
fn test_quantization_type_roundtrip() {
let config = QuantizationConfig {
mode: QuantizationType::PQ {
m: 16,
k: 128,
opq_enabled: true,
oversampling: Some(8),
},
rerank_enabled: false,
rerank_multiplier: 4,
auto_quantization: false,
auto_quantization_threshold: 50_000,
};
let json = serde_json::to_string(&config).expect("serialize");
let roundtripped: QuantizationConfig =
serde_json::from_str(&json).expect("deserialize roundtrip");
assert_eq!(*roundtripped.mode(), *config.mode());
assert_eq!(roundtripped.rerank_enabled, config.rerank_enabled);
assert_eq!(roundtripped.rerank_multiplier, config.rerank_multiplier);
}
#[test]
fn test_quantization_type_default() {
let config = QuantizationConfig::default();
assert_eq!(*config.mode(), QuantizationType::None);
assert!(config.rerank_enabled);
}
#[test]
fn test_quantization_type_should_quantize() {
let config = QuantizationConfig::default();
assert!(config.should_quantize(15_000));
assert!(!config.should_quantize(5_000));
}
#[test]
fn test_quantization_type_is_pq() {
let pq = QuantizationType::PQ {
m: 8,
k: 256,
opq_enabled: false,
oversampling: Some(4),
};
assert!(pq.is_pq());
assert!(!pq.is_rabitq());
assert!(!QuantizationType::SQ8.is_pq());
}
#[test]
fn test_quantization_type_is_rabitq() {
assert!(QuantizationType::RaBitQ.is_rabitq());
assert!(!QuantizationType::RaBitQ.is_pq());
assert!(!QuantizationType::None.is_rabitq());
}
#[test]
fn test_quantization_type_pq_defaults() {
let json = r#"{"mode": {"type": "pq", "m": 8}}"#;
let config: QuantizationConfig =
serde_json::from_str(json).expect("deserialize pq defaults");
match config.mode() {
QuantizationType::PQ {
m,
k,
opq_enabled,
oversampling,
} => {
assert_eq!(*m, 8);
assert_eq!(*k, 256); assert!(!opq_enabled); assert_eq!(*oversampling, Some(4)); }
other => panic!("Expected PQ, got {other:?}"),
}
}
#[test]
fn test_wal_batch_config_default() {
let config = WalBatchConfig::default();
assert!(!config.enabled);
assert_eq!(config.commit_delay_us, 100);
assert_eq!(config.max_batch_size, 128);
}
#[test]
fn test_wal_batch_config_json_roundtrip() {
let config = WalBatchConfig {
enabled: true,
commit_delay_us: 200,
max_batch_size: 256,
};
let json = serde_json::to_string(&config).expect("serialize");
let parsed: WalBatchConfig = serde_json::from_str(&json).expect("deserialize");
assert!(parsed.enabled);
assert_eq!(parsed.commit_delay_us, 200);
assert_eq!(parsed.max_batch_size, 256);
}
#[test]
fn test_wal_batch_config_serde_defaults() {
let json = r"{}";
let config: WalBatchConfig = serde_json::from_str(json).expect("deserialize");
assert!(!config.enabled);
assert_eq!(config.commit_delay_us, 100);
assert_eq!(config.max_batch_size, 128);
}
#[test]
fn test_wal_batch_config_partial_override() {
let json = r#"{"enabled": true}"#;
let config: WalBatchConfig = serde_json::from_str(json).expect("deserialize");
assert!(config.enabled);
assert_eq!(config.commit_delay_us, 100);
assert_eq!(config.max_batch_size, 128);
}
#[test]
fn test_veles_config_includes_wal_batch() {
let config = VelesConfig::default();
assert!(!config.wal_batch.enabled);
assert_eq!(config.wal_batch.max_batch_size, 128);
}
#[test]
fn test_veles_config_toml_with_wal_batch() {
let toml = r"
[wal_batch]
enabled = true
commit_delay_us = 500
max_batch_size = 64
";
let config = VelesConfig::from_toml(toml).expect("parse");
assert!(config.wal_batch.enabled);
assert_eq!(config.wal_batch.commit_delay_us, 500);
assert_eq!(config.wal_batch.max_batch_size, 64);
}
#[test]
fn test_veles_config_toml_roundtrip_with_wal_batch() {
let mut config = VelesConfig::default();
config.wal_batch.enabled = true;
config.wal_batch.max_batch_size = 64;
let toml_str = config.to_toml().expect("serialize");
let parsed = VelesConfig::from_toml(&toml_str).expect("parse");
assert!(parsed.wal_batch.enabled);
assert_eq!(parsed.wal_batch.max_batch_size, 64);
}
}