#![allow(unused_imports)]
pub use crate::utils::{
operators::{OPERATOR_REGISTRY, get_operators_by_category},
vector::VectorInsertQuery,
};
mod casing_tests {
use super::super::*;
#[allow(unused_imports)]
use super::*;
#[test]
fn test_simple_camel_to_snake() {
assert_eq!(to_snake_case("userId"), "user_id");
assert_eq!(to_snake_case("userName"), "user_name");
assert_eq!(to_snake_case("firstName"), "first_name");
}
#[test]
fn test_pascal_to_snake() {
assert_eq!(to_snake_case("UserId"), "user_id");
assert_eq!(to_snake_case("FirstName"), "first_name");
}
#[test]
fn test_consecutive_capitals() {
assert_eq!(to_snake_case("HTTPResponse"), "http_response");
assert_eq!(to_snake_case("XMLParser"), "xml_parser");
assert_eq!(to_snake_case("IOError"), "io_error");
}
#[test]
fn test_already_snake_case() {
assert_eq!(to_snake_case("user_id"), "user_id");
assert_eq!(to_snake_case("first_name"), "first_name");
assert_eq!(to_snake_case("http_response"), "http_response");
}
#[test]
fn test_mixed_formats() {
assert_eq!(to_snake_case("user_Id"), "user_id"); assert_eq!(to_snake_case("HTTPStatus_Code"), "http_status_code");
}
#[test]
fn test_single_char() {
assert_eq!(to_snake_case("a"), "a");
assert_eq!(to_snake_case("A"), "a");
}
#[test]
fn test_empty_string() {
assert_eq!(to_snake_case(""), "");
}
#[test]
fn test_numbers() {
assert_eq!(to_snake_case("phone1"), "phone_1");
assert_eq!(to_snake_case("dns1Id"), "dns_1_id");
assert_eq!(to_snake_case("user2FA"), "user_2_fa");
assert_eq!(to_snake_case("level99Boss"), "level_99_boss");
}
#[test]
fn test_simple_snake_to_camel() {
assert_eq!(to_camel_case("user_id"), "userId");
assert_eq!(to_camel_case("first_name"), "firstName");
assert_eq!(to_camel_case("http_response"), "httpResponse");
}
#[test]
fn test_already_camel_case() {
assert_eq!(to_camel_case("userId"), "userId");
assert_eq!(to_camel_case("firstName"), "firstName");
}
#[test]
fn test_camel_case_collapses_digits() {
assert_eq!(to_camel_case("phone_1"), "phone1");
assert_eq!(to_camel_case("phone_2"), "phone2");
assert_eq!(to_camel_case("dns_1_id"), "dns1Id");
assert_eq!(to_camel_case("line_2_content"), "line2Content");
}
#[test]
fn test_snake_camel_round_trip() {
for snake in [
"id",
"user_id",
"first_name",
"created_at",
"http_response",
"phone_1",
"phone_2",
"address_2",
"dns_1_id",
"line_2_content",
] {
assert_eq!(
to_snake_case(&to_camel_case(snake)),
snake,
"round trip failed for {snake}"
);
}
}
#[test]
fn test_multiple_underscores() {
assert_eq!(to_camel_case("user__id"), "userId");
assert_eq!(to_camel_case("http___response"), "httpResponse");
}
#[test]
fn test_trailing_underscore() {
assert_eq!(to_camel_case("user_id_"), "userId");
assert_eq!(to_camel_case("first_name_"), "firstName");
}
#[test]
fn test_normalize_field_path_simple() {
assert_eq!(normalize_field_path("userId"), "user_id");
assert_eq!(normalize_field_path("createdAt"), "created_at");
}
#[test]
fn test_normalize_field_path_nested() {
assert_eq!(normalize_field_path("user.createdAt"), "user.created_at");
assert_eq!(
normalize_field_path("device.sensorData.currentValue"),
"device.sensor_data.current_value"
);
}
#[test]
fn test_normalize_field_path_already_snake() {
assert_eq!(normalize_field_path("user_id"), "user_id");
assert_eq!(normalize_field_path("user.created_at"), "user.created_at");
}
#[test]
fn test_roundtrip_conversion() {
let original = "userId";
let snake = to_snake_case(original);
let back = to_camel_case(&snake);
assert_eq!(back, original);
let original2 = "HTTPResponse";
let snake2 = to_snake_case(original2);
assert_eq!(snake2, "http_response");
let back2 = to_camel_case(&snake2);
assert_eq!(back2, "httpResponse"); }
#[test]
fn test_real_world_examples() {
assert_eq!(to_snake_case("createdAt"), "created_at");
assert_eq!(to_snake_case("updatedAt"), "updated_at");
assert_eq!(to_snake_case("deletedAt"), "deleted_at");
assert_eq!(to_snake_case("isActive"), "is_active");
assert_eq!(to_snake_case("isDeleted"), "is_deleted");
assert_eq!(to_snake_case("machineId"), "machine_id");
assert_eq!(to_snake_case("deviceType"), "device_type");
}
}
mod opaque_id_tests {
use super::super::*;
#[allow(unused_imports)]
use super::*;
#[test]
fn test_opaque_id_creation() {
let opaque = OpaqueId::new("12345");
assert!(!opaque.as_str().is_empty());
assert!(!opaque.as_str().contains("12345"));
}
#[test]
fn test_opaque_id_decode() {
let db_id = "user_42";
let opaque = OpaqueId::new(db_id);
let decoded = opaque.decode();
assert_eq!(decoded, Some(db_id.to_string()));
}
#[test]
fn test_opaque_id_with_signature() {
let db_id = "12345";
let secret = b"secret_key";
let opaque = OpaqueId::with_signature(db_id, secret);
assert!(opaque.verify_signature(secret));
assert!(!opaque.verify_signature(b"wrong_secret"));
}
#[test]
fn test_opaque_id_signature_tampering() {
let db_id = "sensitive_id_789";
let secret = b"super_secret";
let mut opaque = OpaqueId::with_signature(db_id, secret);
assert!(opaque.verify_signature(secret));
opaque.id = opaque.id.chars().rev().collect();
assert!(!opaque.verify_signature(secret));
}
#[test]
fn test_opaque_id_equality() {
let opaque1 = OpaqueId::new("same_id");
let opaque2 = OpaqueId::new("same_id");
assert_eq!(opaque1, opaque2);
let opaque3 = OpaqueId::new("different_id");
assert_ne!(opaque1, opaque3);
}
#[test]
fn test_opaque_id_prevents_enumeration() {
let ids: Vec<String> = (1..=5).map(|i| i.to_string()).collect();
let opaque_ids: Vec<OpaqueId> = ids.iter().map(OpaqueId::new).collect();
for i in 1..opaque_ids.len() {
assert_ne!(opaque_ids[i].as_str(), opaque_ids[i - 1].as_str());
}
for i in 0..opaque_ids.len() {
let original = ids[i].as_str();
let opaque = opaque_ids[i].as_str();
assert!(!opaque.contains(original));
}
}
}
mod operators_tests {
#![allow(clippy::unwrap_used)]
use super::{super::*, *};
#[test]
fn test_operator_registry_initialized() {
assert!(OPERATOR_REGISTRY.len() >= 40);
}
#[test]
fn test_comparison_operators() {
let operators = ["eq", "ne", "gt", "gte", "lt", "lte", "in", "nin"];
for op_name in &operators {
let op = get_operator_info(op_name);
assert!(op.is_some(), "Operator {op_name} should exist");
let op = op.unwrap();
assert_eq!(op.category, OperatorCategory::Comparison);
assert!(!op.jsonb_operator);
}
}
#[test]
fn test_string_operators() {
let operators = [
"like", "ilike", "nlike", "nilike", "regex", "iregex", "nregex", "niregex",
];
for op_name in &operators {
let op = get_operator_info(op_name);
assert!(op.is_some(), "String operator {op_name} should exist");
let op = op.unwrap();
assert_eq!(op.category, OperatorCategory::String);
}
}
#[test]
fn test_null_operators() {
let op1 = get_operator_info("is_null").unwrap();
assert_eq!(op1.sql_op, "IS NULL");
assert_eq!(op1.category, OperatorCategory::Null);
let op2 = get_operator_info("is_not_null").unwrap();
assert_eq!(op2.sql_op, "IS NOT NULL");
assert_eq!(op2.category, OperatorCategory::Null);
}
#[test]
fn test_containment_operators() {
let operators = [
"contains",
"contained_in",
"has_key",
"has_any_keys",
"has_all_keys",
];
for op_name in &operators {
let op = get_operator_info(op_name);
assert!(op.is_some(), "Containment operator {op_name} should exist");
let op = op.unwrap();
assert_eq!(op.category, OperatorCategory::Containment);
assert!(op.jsonb_operator, "{op_name} should be JSONB operator");
}
}
#[test]
fn test_array_operators() {
let operators = ["array_contains", "array_contained_in", "array_overlaps"];
for op_name in &operators {
let op = get_operator_info(op_name);
assert!(op.is_some(), "Array operator {op_name} should exist");
let op = op.unwrap();
assert_eq!(op.category, OperatorCategory::Array);
}
}
#[test]
fn test_vector_operators() {
let operators = [
("cosine_distance", "<=>"),
("l2_distance", "<->"),
("inner_product", "<#>"),
("l1_distance", "<+>"),
("hamming_distance", "<~>"),
("jaccard_distance", "<%>"),
];
for (op_name, expected_sql) in &operators {
let op = get_operator_info(op_name);
assert!(op.is_some(), "Vector operator {op_name} should exist");
let op = op.unwrap();
assert_eq!(op.category, OperatorCategory::Vector);
assert_eq!(op.sql_op, *expected_sql);
}
}
#[test]
fn test_fulltext_operators() {
let operators = [
"search",
"plainto_tsquery",
"phraseto_tsquery",
"websearch_to_tsquery",
];
for op_name in &operators {
let op = get_operator_info(op_name);
assert!(op.is_some(), "Fulltext operator {op_name} should exist");
let op = op.unwrap();
assert_eq!(op.category, OperatorCategory::Fulltext);
assert_eq!(op.sql_op, "@@");
}
}
#[test]
fn test_is_operator() {
assert!(is_operator("eq"));
assert!(is_operator("contains"));
assert!(is_operator("cosine_distance"));
assert!(!is_operator("invalid_operator"));
assert!(!is_operator(""));
}
#[test]
fn test_get_operators_by_category() {
let comparison_ops = get_operators_by_category(OperatorCategory::Comparison);
assert!(comparison_ops.len() >= 8);
let vector_ops = get_operators_by_category(OperatorCategory::Vector);
assert!(vector_ops.len() >= 6);
let fulltext_ops = get_operators_by_category(OperatorCategory::Fulltext);
assert!(fulltext_ops.len() >= 4);
}
#[test]
fn test_requires_array_flag() {
assert!(get_operator_info("in").unwrap().requires_array);
assert!(get_operator_info("nin").unwrap().requires_array);
assert!(!get_operator_info("eq").unwrap().requires_array);
assert!(!get_operator_info("like").unwrap().requires_array);
}
#[test]
fn test_jsonb_operator_flag() {
assert!(get_operator_info("contains").unwrap().jsonb_operator);
assert!(get_operator_info("has_key").unwrap().jsonb_operator);
assert!(!get_operator_info("eq").unwrap().jsonb_operator);
assert!(!get_operator_info("like").unwrap().jsonb_operator);
}
}
mod vector_tests {
use super::{super::*, *};
use crate::schema::{DistanceMetric, VectorConfig};
#[test]
fn test_similarity_search_basic() {
let builder = VectorQueryBuilder::new();
let query = VectorSearchQuery::new("documents")
.with_embedding_column("embedding")
.with_limit(10);
let embedding = vec![0.1, 0.2, 0.3];
let (sql, params) = builder.similarity_search(&query, &embedding);
assert!(sql.contains("SELECT *"));
assert!(sql.contains("FROM documents"));
assert!(sql.contains("ORDER BY embedding <=>"));
assert!(sql.contains("$1::vector"));
assert!(sql.contains("LIMIT $2"));
assert_eq!(params.len(), 2);
}
#[test]
fn test_similarity_search_with_columns() {
let builder = VectorQueryBuilder::new();
let query = VectorSearchQuery::new("docs")
.with_select_columns(vec!["id".to_string(), "content".to_string()])
.with_distance_score()
.with_limit(5);
let embedding = vec![0.1, 0.2];
let (sql, params) = builder.similarity_search(&query, &embedding);
assert!(sql.contains("SELECT id, content,"));
assert!(sql.contains("AS distance"));
assert!(sql.contains("LIMIT $2"));
assert_eq!(params.len(), 2);
}
#[test]
fn test_similarity_search_with_where() {
let builder = VectorQueryBuilder::new();
let query = VectorSearchQuery::new("documents")
.with_where("metadata->>'type' = 'article'")
.with_limit(10);
let embedding = vec![0.1, 0.2, 0.3];
let (sql, _) = builder.similarity_search(&query, &embedding);
assert!(sql.contains("WHERE metadata->>'type' = 'article'"));
}
#[test]
fn test_similarity_search_with_offset() {
let builder = VectorQueryBuilder::new();
let query = VectorSearchQuery::new("documents").with_limit(10).with_offset(20);
let embedding = vec![0.1, 0.2];
let (sql, params) = builder.similarity_search(&query, &embedding);
assert!(sql.contains("OFFSET $3"));
assert_eq!(params.len(), 3);
}
#[test]
fn test_similarity_search_l2_distance() {
let builder = VectorQueryBuilder::new();
let query = VectorSearchQuery::new("docs")
.with_distance_metric(DistanceMetric::L2)
.with_limit(5);
let embedding = vec![0.1, 0.2];
let (sql, _) = builder.similarity_search(&query, &embedding);
assert!(sql.contains("<->"));
}
#[test]
fn test_similarity_search_inner_product() {
let builder = VectorQueryBuilder::new();
let query = VectorSearchQuery::new("docs")
.with_distance_metric(DistanceMetric::InnerProduct)
.with_limit(5);
let embedding = vec![0.1, 0.2];
let (sql, _) = builder.similarity_search(&query, &embedding);
assert!(sql.contains("<#>"));
}
#[test]
fn test_insert_one_basic() {
let builder = VectorQueryBuilder::new();
let query = VectorInsertQuery::new("documents").with_columns(vec![
"id".to_string(),
"content".to_string(),
"embedding".to_string(),
]);
let values = vec![
VectorParam::String("doc1".to_string()),
VectorParam::String("Hello world".to_string()),
VectorParam::Vector(vec![0.1, 0.2, 0.3]),
];
let (sql, params) = builder.insert_one(&query, &values);
assert!(sql.contains("INSERT INTO documents (id, content, embedding)"));
assert!(sql.contains("VALUES ($1, $2, $3::vector)"));
assert!(sql.contains("RETURNING id"));
assert_eq!(params.len(), 3);
}
#[test]
fn test_insert_upsert() {
let builder = VectorQueryBuilder::new();
let query = VectorInsertQuery::new("documents")
.with_columns(vec![
"id".to_string(),
"content".to_string(),
"embedding".to_string(),
])
.with_upsert(vec!["id".to_string()]);
let values = vec![
VectorParam::String("doc1".to_string()),
VectorParam::String("Hello world".to_string()),
VectorParam::Vector(vec![0.1, 0.2, 0.3]),
];
let (sql, _) = builder.insert_one(&query, &values);
assert!(sql.contains("ON CONFLICT (id) DO UPDATE SET"));
assert!(sql.contains("content = EXCLUDED.content"));
assert!(sql.contains("embedding = EXCLUDED.embedding"));
}
#[test]
fn test_insert_batch() {
let builder = VectorQueryBuilder::new();
let query = VectorInsertQuery::new("documents")
.with_columns(vec!["id".to_string(), "embedding".to_string()]);
let rows = vec![
vec![
VectorParam::String("doc1".to_string()),
VectorParam::Vector(vec![0.1, 0.2]),
],
vec![
VectorParam::String("doc2".to_string()),
VectorParam::Vector(vec![0.3, 0.4]),
],
];
let (sql, params) = builder.insert_batch(&query, &rows);
assert!(sql.contains("INSERT INTO documents (id, embedding)"));
assert!(sql.contains("($1, $2::vector)"));
assert!(sql.contains("($3, $4::vector)"));
assert_eq!(params.len(), 4);
}
#[test]
fn test_insert_batch_empty() {
let builder = VectorQueryBuilder::new();
let query = VectorInsertQuery::new("documents").with_columns(vec!["id".to_string()]);
let (sql, params) = builder.insert_batch(&query, &[]);
assert!(sql.is_empty());
assert!(params.is_empty());
}
#[test]
fn test_create_index_hnsw() {
let builder = VectorQueryBuilder::new();
let config = VectorConfig::openai();
let sql = builder.create_index(&config, "documents", "embedding");
assert_eq!(
sql,
Some("CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)".to_string())
);
}
#[test]
fn test_create_index_ivfflat() {
let builder = VectorQueryBuilder::new();
let config = VectorConfig::new(1536)
.with_index(crate::schema::VectorIndexType::IvfFlat)
.with_distance(DistanceMetric::L2);
let sql = builder.create_index(&config, "docs", "vec");
assert_eq!(sql, Some("CREATE INDEX ON docs USING ivfflat (vec vector_l2_ops)".to_string()));
}
#[test]
fn test_create_index_none() {
let builder = VectorQueryBuilder::new();
let config = VectorConfig::new(1536).with_index(crate::schema::VectorIndexType::None);
let sql = builder.create_index(&config, "documents", "embedding");
assert_eq!(sql, None);
}
#[test]
fn test_vector_param_to_sql_literal() {
let vec_param = VectorParam::Vector(vec![0.1, 0.2, 0.3]);
assert_eq!(vec_param.to_sql_literal(), "'[0.1,0.2,0.3]'::vector");
let int_param = VectorParam::Int(42);
assert_eq!(int_param.to_sql_literal(), "42");
let str_param = VectorParam::String("hello".to_string());
assert_eq!(str_param.to_sql_literal(), "'hello'");
let str_param_escape = VectorParam::String("it's a test".to_string());
assert_eq!(str_param_escape.to_sql_literal(), "'it''s a test'");
}
#[test]
fn test_question_mark_placeholders() {
let builder = VectorQueryBuilder::with_question_marks();
let query = VectorSearchQuery::new("docs").with_limit(10);
let embedding = vec![0.1, 0.2];
let (sql, _) = builder.similarity_search(&query, &embedding);
assert!(sql.contains("?::vector"));
assert!(!sql.contains("$1"));
}
}