#![allow(deprecated)]
use super::*;
use crate::point::Point;
use crate::{CollectionType, DistanceMetric};
use tempfile::tempdir;
#[test]
fn test_create_get_delete_lifecycle() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
db.create_collection("lifecycle", 128, DistanceMetric::Cosine)
.unwrap();
assert_eq!(db.list_collections(), vec!["lifecycle"]);
let coll = db.get_collection("lifecycle");
assert!(coll.is_some());
assert_eq!(coll.unwrap().config().dimension, 128);
db.delete_collection("lifecycle").unwrap();
assert!(db.list_collections().is_empty());
assert!(db.get_collection("lifecycle").is_none());
}
#[test]
fn test_create_duplicate_returns_collection_exists() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
db.create_collection("dup", 64, DistanceMetric::Euclidean)
.unwrap();
let err = db
.create_collection("dup", 64, DistanceMetric::Euclidean)
.unwrap_err();
assert!(matches!(err, crate::Error::CollectionExists(_)));
}
#[test]
fn test_create_vector_duplicate_returns_error() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
db.create_vector_collection("vec_dup", 32, DistanceMetric::Cosine)
.unwrap();
let err = db
.create_vector_collection("vec_dup", 32, DistanceMetric::Cosine)
.unwrap_err();
assert!(matches!(err, crate::Error::CollectionExists(_)));
}
#[test]
fn test_create_metadata_duplicate_returns_error() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
db.create_metadata_collection("meta_dup").unwrap();
let err = db.create_metadata_collection("meta_dup").unwrap_err();
assert!(matches!(err, crate::Error::CollectionExists(_)));
}
#[test]
fn test_get_nonexistent_returns_none() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert!(db.get_collection("nope").is_none());
assert!(db.get_vector_collection("nope").is_none());
assert!(db.get_graph_collection("nope").is_none());
assert!(db.get_metadata_collection("nope").is_none());
}
#[test]
fn test_delete_nonexistent_returns_not_found() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
let err = db.delete_collection("absent").unwrap_err();
assert!(matches!(err, crate::Error::CollectionNotFound(_)));
}
#[test]
fn test_multi_collection_data_isolation() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
db.create_collection("coll_a", 4, DistanceMetric::Cosine)
.unwrap();
db.create_collection("coll_b", 4, DistanceMetric::Euclidean)
.unwrap();
let coll_a = db.get_collection("coll_a").unwrap();
coll_a
.upsert(vec![Point::new(
1,
vec![1.0, 0.0, 0.0, 0.0],
Some(serde_json::json!({"source": "a"})),
)])
.unwrap();
let coll_b = db.get_collection("coll_b").unwrap();
assert!(coll_b.get(&[1]).into_iter().flatten().next().is_none());
}
#[test]
fn test_list_collections_returns_sorted_names() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
db.create_collection("zebra", 4, DistanceMetric::Cosine)
.unwrap();
db.create_collection("alpha", 4, DistanceMetric::Cosine)
.unwrap();
db.create_collection("middle", 4, DistanceMetric::Cosine)
.unwrap();
let names = db.list_collections();
assert_eq!(names, vec!["alpha", "middle", "zebra"]);
}
#[test]
fn test_create_collection_typed_metadata_only() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
db.create_collection_typed("products", &CollectionType::MetadataOnly)
.unwrap();
assert!(db.get_metadata_collection("products").is_some());
assert_eq!(db.list_collections(), vec!["products"]);
}
#[test]
fn test_create_collection_typed_vector() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
let kind = CollectionType::Vector {
dimension: 128,
metric: DistanceMetric::DotProduct,
storage_mode: crate::StorageMode::Full,
};
db.create_collection_typed("embeddings", &kind).unwrap();
let vc = db.get_vector_collection("embeddings").unwrap();
assert_eq!(vc.inner.config().dimension, 128);
assert_eq!(vc.inner.config().metric, DistanceMetric::DotProduct);
}
#[test]
fn test_delete_collection_removes_disk_directory() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
db.create_collection("cleanup", 4, DistanceMetric::Cosine)
.unwrap();
let coll_path = dir.path().join("cleanup");
assert!(coll_path.exists(), "collection dir should exist on disk");
db.delete_collection("cleanup").unwrap();
assert!(
!coll_path.exists(),
"collection dir should be removed after delete"
);
}
#[test]
fn test_name_collision_across_collection_types() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
db.create_metadata_collection("shared_name").unwrap();
let err = db
.create_vector_collection("shared_name", 64, DistanceMetric::Cosine)
.unwrap_err();
assert!(matches!(err, crate::Error::CollectionExists(_)));
}
fn assert_name_rejected(db: &Database, name: &str) {
let err = db
.create_collection(name, 4, DistanceMetric::Cosine)
.unwrap_err();
assert!(
matches!(err, crate::Error::InvalidCollectionName { .. }),
"Expected InvalidCollectionName for {:?}, got: {err}",
name,
);
}
#[test]
fn test_reject_empty_name() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert_name_rejected(&db, "");
}
#[test]
fn test_reject_dot_names() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert_name_rejected(&db, ".");
assert_name_rejected(&db, "..");
}
#[test]
fn test_reject_path_traversal_unix() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert_name_rejected(&db, "../etc/evil");
assert_name_rejected(&db, "../../passwd");
assert_name_rejected(&db, "foo/bar");
assert_name_rejected(&db, "a/b/c");
}
#[test]
fn test_reject_path_traversal_windows() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert_name_rejected(&db, r"..\evil");
assert_name_rejected(&db, r"foo\bar");
assert_name_rejected(&db, r"C:\Windows");
}
#[test]
fn test_reject_path_separators() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert_name_rejected(&db, "name/with/slash");
assert_name_rejected(&db, "name\\with\\backslash");
}
#[test]
fn test_reject_special_characters() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert_name_rejected(&db, "name with spaces");
assert_name_rejected(&db, "name@special");
assert_name_rejected(&db, "name.dot");
assert_name_rejected(&db, "name#hash");
assert_name_rejected(&db, "name$dollar");
assert_name_rejected(&db, "name:colon");
assert_name_rejected(&db, "name*star");
assert_name_rejected(&db, "name?question");
assert_name_rejected(&db, "name<angle>");
assert_name_rejected(&db, "name|pipe");
assert_name_rejected(&db, "name\"quote");
}
#[test]
fn test_reject_unicode() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert_name_rejected(&db, "café");
assert_name_rejected(&db, "日本語");
assert_name_rejected(&db, "коллекция");
}
#[test]
fn test_reject_leading_hyphen() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert_name_rejected(&db, "-leading");
assert_name_rejected(&db, "--double");
}
#[test]
fn test_reject_too_long_name() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
let long_name = "a".repeat(129);
assert_name_rejected(&db, &long_name);
}
#[test]
fn test_reject_windows_reserved_names() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
for reserved in &[
"CON", "PRN", "AUX", "NUL", "COM1", "COM9", "LPT1", "LPT9", "con", "Con", "aux",
] {
assert_name_rejected(&db, reserved);
}
}
#[test]
fn test_accept_valid_names() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
for name in &[
"simple",
"with_underscore",
"with-hyphen",
"CamelCase",
"UPPERCASE",
"a",
"a1",
"collection_v2",
"test-123",
&"a".repeat(128), ] {
db.create_collection(name, 4, DistanceMetric::Cosine)
.unwrap_or_else(|e| panic!("Expected name {:?} to be valid, got: {e}", name));
}
}
#[test]
fn test_reject_invalid_name_on_vector_collection() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
let err = db
.create_vector_collection("../evil", 4, DistanceMetric::Cosine)
.unwrap_err();
assert!(matches!(err, crate::Error::InvalidCollectionName { .. }));
}
#[test]
fn test_reject_invalid_name_on_graph_collection() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
let err = db
.create_graph_collection("../evil", crate::collection::GraphSchema::schemaless())
.unwrap_err();
assert!(matches!(err, crate::Error::InvalidCollectionName { .. }));
}
#[test]
fn test_reject_invalid_name_on_metadata_collection() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
let err = db.create_metadata_collection("../evil").unwrap_err();
assert!(matches!(err, crate::Error::InvalidCollectionName { .. }));
}
#[test]
fn test_reject_invalid_name_on_delete() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
let err = db.delete_collection("../evil").unwrap_err();
assert!(matches!(err, crate::Error::InvalidCollectionName { .. }));
}
#[test]
fn test_reject_invalid_name_on_get_stats() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
let err = db.get_collection_stats("../evil").unwrap_err();
assert!(matches!(err, crate::Error::InvalidCollectionName { .. }));
}
#[test]
fn test_reject_invalid_name_on_analyze() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
let err = db.analyze_collection("../evil").unwrap_err();
assert!(matches!(err, crate::Error::InvalidCollectionName { .. }));
}
#[test]
fn test_get_vector_collection_rejects_traversal() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert!(db.get_vector_collection("../evil").is_none());
}
#[test]
fn test_get_graph_collection_rejects_traversal() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert!(db.get_graph_collection("../evil").is_none());
}
#[test]
fn test_get_metadata_collection_rejects_traversal() {
let dir = tempdir().unwrap();
let db = Database::open(dir.path()).unwrap();
assert!(db.get_metadata_collection("../evil").is_none());
}