#![allow(non_snake_case)]
use std::collections::HashMap;
use crate::base::test_support::{TestUser, seed_users, test_user};
use crate::{OrderDirection, Persistence, Querying, Relation};
use rustrails_support::{database, runtime};
use sea_orm::{ConnectionTrait, Schema};
use serde_json::{Value, json};
use rustrails_support::ignored_rails_tests;
fn run_finder_test(seed: bool, test: impl FnOnce() + Send + 'static) {
std::thread::spawn(move || {
let _rt = runtime::init_runtime();
database::establish("sqlite::memory:").expect("sqlite in-memory connection should succeed");
runtime::block_on(async {
let db = database::db();
let schema = Schema::new(db.get_database_backend());
db.execute(&schema.create_table_from_entity(test_user::Entity))
.await
.expect("test_users table should be created");
if seed {
seed_users(&db).await;
}
});
test();
})
.join()
.expect("finder test thread should not panic");
}
fn run_seeded_finder_test(test: impl FnOnce() + Send + 'static) {
run_finder_test(true, test);
}
fn run_empty_finder_test(test: impl FnOnce() + Send + 'static) {
run_finder_test(false, test);
}
fn load_relation(relation: Relation<TestUser>) -> Result<Vec<TestUser>, crate::RecordError> {
relation.load_sync()
}
fn first_relation(relation: Relation<TestUser>) -> Result<Option<TestUser>, crate::RecordError> {
relation.first_sync()
}
fn last_relation(relation: Relation<TestUser>) -> Result<Option<TestUser>, crate::RecordError> {
relation.last_sync()
}
fn relation_contains(
relation: Relation<TestUser>,
user: &TestUser,
) -> Result<bool, crate::RecordError> {
Ok(load_relation(relation)?.contains(user))
}
fn insert_user(name: &str, email: &str) -> TestUser {
TestUser::create_sync(HashMap::from([
("name".to_owned(), json!(name)),
("email".to_owned(), json!(email)),
]))
.expect("fixture insert should succeed")
}
#[test]
fn test_find() {
run_seeded_finder_test(|| {
let user = TestUser::find_sync(1).expect("find_sync should return the seeded record");
assert_eq!(user.id, Some(1));
assert_eq!(user.name, "Alice");
assert_eq!(user.email, "alice@example.com");
});
}
#[test]
fn test_exists() {
run_seeded_finder_test(|| {
assert!(
TestUser::exists_sync(HashMap::from([("id".to_owned(), json!(1))]))
.expect("exists_sync should find the seeded id")
);
assert!(
TestUser::exists_sync(HashMap::from([("name".to_owned(), json!("Alice"))]))
.expect("exists_sync should find the seeded name")
);
assert!(
!TestUser::exists_sync(HashMap::from([("id".to_owned(), json!(45))]))
.expect("exists_sync should return false for a missing id")
);
assert!(
!TestUser::exists_sync(HashMap::from([("id".to_owned(), json!(i64::MAX))]))
.expect("exists_sync should return false for a very large missing id")
);
});
}
#[test]
fn test_exists_with_scope() {
run_seeded_finder_test(|| {
assert!(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Bob"))]))
.exists_sync()
.expect("scoped exists should succeed")
);
assert!(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Bob"))]))
.r#where(HashMap::from([("id".to_owned(), json!(2))]))
.exists_sync()
.expect("scoped exists should match the seeded Bob row")
);
assert!(
!Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Nobody"))]))
.exists_sync()
.expect("empty scoped exists should succeed")
);
});
}
#[test]
fn test_exists_uses_existing_scope() {
run_seeded_finder_test(|| {
assert!(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Carol"))]))
.r#where(HashMap::from([("id".to_owned(), json!(3))]))
.exists_sync()
.expect("existing scope should compose with later filters")
);
assert!(
!Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Carol"))]))
.r#where(HashMap::from([("id".to_owned(), json!(1))]))
.exists_sync()
.expect("existing scope should reject ids outside the scope")
);
});
}
#[test]
fn test_exists_returns_true_with_one_record_and_no_args() {
run_seeded_finder_test(|| {
assert!(TestUser::exists_sync(HashMap::new()).expect("exists_sync should succeed"));
});
}
#[test]
fn test_exists_with_loaded_relation() {
run_seeded_finder_test(|| {
let loaded = load_relation(Relation::<TestUser>::new()).expect("relation should load");
assert_eq!(loaded.len(), 3);
assert!(
Relation::<TestUser>::new()
.exists_sync()
.expect("exists should succeed")
);
});
}
#[test]
fn test_exists_with_empty_loaded_relation() {
run_empty_finder_test(|| {
let loaded = load_relation(Relation::<TestUser>::new()).expect("relation should load");
assert!(loaded.is_empty());
assert!(
!Relation::<TestUser>::new()
.exists_sync()
.expect("exists should be false for an empty table")
);
});
}
#[test]
fn test_exists_with_nil_arg() {
run_seeded_finder_test(|| {
assert!(
!TestUser::exists_sync(HashMap::from([("id".to_owned(), Value::Null)]))
.expect("null primary-key predicate should be false")
);
assert!(TestUser::exists_sync(HashMap::new()).expect("unscoped exists should succeed"));
});
}
#[test]
fn test_exists_with_empty_hash_arg() {
run_seeded_finder_test(|| {
assert!(TestUser::exists_sync(HashMap::new()).expect("exists_sync should succeed"));
});
}
#[test]
fn test_exists_with_distinct_and_offset_and_select() {
run_seeded_finder_test(|| {
insert_user("Bob", "bobby@example.com");
assert!(
Relation::<TestUser>::new()
.select(&["name"])
.distinct()
.order("name", OrderDirection::Asc)
.offset(2)
.exists_sync()
.expect("distinct offset relation should still have one row")
);
assert!(
!Relation::<TestUser>::new()
.select(&["name"])
.distinct()
.order("name", OrderDirection::Asc)
.offset(3)
.exists_sync()
.expect("distinct offset relation should become empty past the last row")
);
});
}
#[test]
fn test_exists_with_order_and_distinct() {
run_seeded_finder_test(|| {
assert!(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.distinct()
.exists_sync()
.expect("ordered distinct exists should succeed")
);
});
}
#[test]
fn test_exists_with_order() {
run_seeded_finder_test(|| {
assert!(
Relation::<TestUser>::new()
.order("id", OrderDirection::Desc)
.exists_sync()
.expect("ordered exists should succeed")
);
});
}
#[test]
fn test_exists_with_large_number() {
run_seeded_finder_test(|| {
assert!(
TestUser::exists_sync(HashMap::from([("id".to_owned(), json!(1))]))
.expect("seeded id should exist")
);
assert!(
!TestUser::exists_sync(HashMap::from([("id".to_owned(), json!(i64::MAX))]))
.expect("very large missing id should not exist")
);
});
}
#[test]
fn test_exists_with_empty_table_and_no_args_given() {
run_empty_finder_test(|| {
assert!(
!TestUser::exists_sync(HashMap::new())
.expect("exists_sync should be false for an empty table")
);
});
}
#[test]
fn test_include_on_unloaded_relation_with_match() {
run_seeded_finder_test(|| {
let bob = TestUser::find_sync(2).expect("Bob should be present");
assert!(
relation_contains(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Bob"))])),
&bob,
)
.expect("relation membership should succeed")
);
});
}
#[test]
fn test_include_on_unloaded_relation_without_match() {
run_seeded_finder_test(|| {
let alice = TestUser::find_sync(1).expect("Alice should be present");
assert!(
!relation_contains(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Bob"))])),
&alice,
)
.expect("relation membership should succeed")
);
});
}
#[test]
fn test_include_on_unloaded_relation_with_offset() {
run_seeded_finder_test(|| {
let bob = TestUser::find_sync(2).expect("Bob should be present");
assert!(
relation_contains(
Relation::<TestUser>::new()
.order("name", OrderDirection::Asc)
.offset(1),
&bob,
)
.expect("offset membership should succeed")
);
});
}
#[test]
fn test_include_on_unloaded_relation_with_limit() {
run_seeded_finder_test(|| {
let alice = TestUser::find_sync(1).expect("Alice should be present");
let bob = TestUser::find_sync(2).expect("Bob should be present");
let carol = TestUser::find_sync(3).expect("Carol should be present");
let relation = Relation::<TestUser>::new()
.order("id", OrderDirection::Desc)
.limit(2);
assert!(!relation_contains(relation, &alice).expect("membership should succeed"));
assert!(
relation_contains(
Relation::<TestUser>::new()
.order("id", OrderDirection::Desc)
.limit(2),
&bob,
)
.expect("membership should succeed")
);
assert!(
relation_contains(
Relation::<TestUser>::new()
.order("id", OrderDirection::Desc)
.limit(2),
&carol,
)
.expect("membership should succeed")
);
});
}
#[test]
fn test_include_on_loaded_relation_with_match() {
run_seeded_finder_test(|| {
let bob = TestUser::find_sync(2).expect("Bob should be present");
let users = load_relation(
Relation::<TestUser>::new().r#where(HashMap::from([("name".to_owned(), json!("Bob"))])),
)
.expect("relation should load");
assert!(users.contains(&bob));
});
}
#[test]
fn test_include_on_loaded_relation_without_match() {
run_seeded_finder_test(|| {
let alice = TestUser::find_sync(1).expect("Alice should be present");
let users = load_relation(
Relation::<TestUser>::new().r#where(HashMap::from([("name".to_owned(), json!("Bob"))])),
)
.expect("relation should load");
assert!(!users.contains(&alice));
});
}
#[test]
fn test_member_on_unloaded_relation_with_match() {
run_seeded_finder_test(|| {
let carol = TestUser::find_sync(3).expect("Carol should be present");
assert!(
relation_contains(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Carol"))])),
&carol,
)
.expect("relation membership should succeed")
);
});
}
#[test]
fn test_member_on_unloaded_relation_without_match() {
run_seeded_finder_test(|| {
let alice = TestUser::find_sync(1).expect("Alice should be present");
assert!(
!relation_contains(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Carol"))])),
&alice,
)
.expect("relation membership should succeed")
);
});
}
#[test]
fn test_member_on_unloaded_relation_with_offset() {
run_seeded_finder_test(|| {
let bob = TestUser::find_sync(2).expect("Bob should be present");
assert!(
relation_contains(
Relation::<TestUser>::new()
.order("name", OrderDirection::Asc)
.offset(1),
&bob,
)
.expect("offset membership should succeed")
);
});
}
#[test]
fn test_member_on_unloaded_relation_with_limit() {
run_seeded_finder_test(|| {
let alice = TestUser::find_sync(1).expect("Alice should be present");
let bob = TestUser::find_sync(2).expect("Bob should be present");
let carol = TestUser::find_sync(3).expect("Carol should be present");
assert!(
!relation_contains(
Relation::<TestUser>::new()
.order("id", OrderDirection::Desc)
.limit(2),
&alice,
)
.expect("membership should succeed")
);
assert!(
relation_contains(
Relation::<TestUser>::new()
.order("id", OrderDirection::Desc)
.limit(2),
&bob,
)
.expect("membership should succeed")
);
assert!(
relation_contains(
Relation::<TestUser>::new()
.order("id", OrderDirection::Desc)
.limit(2),
&carol,
)
.expect("membership should succeed")
);
});
}
#[test]
fn test_member_on_loaded_relation_with_match() {
run_seeded_finder_test(|| {
let carol = TestUser::find_sync(3).expect("Carol should be present");
let users = load_relation(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Carol"))])),
)
.expect("relation should load");
assert!(users.contains(&carol));
});
}
#[test]
fn test_member_on_loaded_relation_without_match() {
run_seeded_finder_test(|| {
let alice = TestUser::find_sync(1).expect("Alice should be present");
let users = load_relation(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Carol"))])),
)
.expect("relation should load");
assert!(!users.contains(&alice));
});
}
#[test]
fn test_find_with_large_number() {
run_seeded_finder_test(|| {
let error =
TestUser::find_sync(i64::MAX).expect_err("very large missing id should not resolve");
assert!(matches!(error, crate::RecordError::NotFound));
});
}
#[test]
fn test_find_by_with_large_number() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_sync(HashMap::from([("id".to_owned(), json!(i64::MAX))]))
.expect("find_by_sync should succeed");
assert!(user.is_none());
});
}
#[test]
fn test_find_by_id_with_large_number() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_id_sync(i64::MAX).expect("find_by_id_sync should succeed");
assert!(user.is_none());
});
}
#[test]
fn test_find_on_relation_with_large_number() {
run_seeded_finder_test(|| {
let users = load_relation(
Relation::<TestUser>::new()
.r#where(HashMap::from([("id".to_owned(), json!(i64::MAX))])),
)
.expect("relation should load");
assert!(users.is_empty());
});
}
#[test]
fn test_find_by_on_relation_with_large_number() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.r#where(HashMap::from([("id".to_owned(), json!(i64::MAX))])),
)
.expect("relation should query successfully");
assert!(user.is_none());
});
}
#[test]
fn test_first() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new().r#where(HashMap::from([("name".to_owned(), json!("Bob"))])),
)
.expect("first relation query should succeed")
.expect("Bob should match the scoped first query");
assert_eq!(user.id, Some(2));
assert_eq!(user.email, "bob@example.com");
});
}
#[test]
fn test_first_failing() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Missing"))])),
)
.expect("first relation query should succeed");
assert!(user.is_none());
});
}
#[test]
fn test_first_have_primary_key_order_by_default() {
run_seeded_finder_test(|| {
let first = TestUser::first_sync()
.expect("first_sync should succeed")
.expect("seeded table should have a first row");
let limited = first_relation(Relation::<TestUser>::new().limit(5))
.expect("limited relation query should succeed")
.expect("limited relation should still have a first row");
assert_eq!(first.id, Some(1));
assert_eq!(limited.id, Some(1));
});
}
#[test]
fn test_last_on_relation_with_limit_and_offset() {
run_seeded_finder_test(|| {
let limited_last = last_relation(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.limit(2),
)
.expect("limited last query should succeed")
.expect("limited relation should still have a last row");
let offset_last = last_relation(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(1)
.limit(2),
)
.expect("offset last query should succeed")
.expect("offset relation should still have a last row");
assert_eq!(limited_last.id, Some(2));
assert_eq!(offset_last.id, Some(3));
});
}
#[test]
fn test_first_on_relation_with_limit_and_offset() {
run_seeded_finder_test(|| {
let limited_first = first_relation(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.limit(2),
)
.expect("limited first query should succeed")
.expect("limited relation should still have a first row");
let offset_first = first_relation(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(1)
.limit(2),
)
.expect("offset first query should succeed")
.expect("offset relation should still have a first row");
assert_eq!(limited_first.id, Some(1));
assert_eq!(offset_first.id, Some(2));
});
}
#[test]
fn test_find_by_one_attribute() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_sync(HashMap::from([("name".to_owned(), json!("Alice"))]))
.expect("find_by_sync should succeed")
.expect("Alice should match the single-attribute lookup");
let missing =
TestUser::find_by_sync(HashMap::from([("name".to_owned(), json!("Missing"))]))
.expect("missing lookup should still query successfully");
assert_eq!(user.id, Some(1));
assert!(missing.is_none());
});
}
#[test]
fn test_find_by_two_attributes() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_sync(HashMap::from([
("name".to_owned(), json!("Alice")),
("email".to_owned(), json!("alice@example.com")),
]))
.expect("find_by_sync should succeed")
.expect("both attributes should match the seeded Alice row");
let missing = TestUser::find_by_sync(HashMap::from([
("name".to_owned(), json!("Alice")),
("email".to_owned(), json!("bob@example.com")),
]))
.expect("partial mismatch should still query successfully");
assert_eq!(user.id, Some(1));
assert!(missing.is_none());
});
}
#[test]
fn test_find_by_and_where_consistency_with_active_record_instance() {
run_seeded_finder_test(|| {
let from_find = TestUser::find_sync(2).expect("find_sync should return Bob");
let from_where = first_relation(
Relation::<TestUser>::new().r#where(HashMap::from([("id".to_owned(), json!(2))])),
)
.expect("relation query should succeed")
.expect("where relation should return Bob");
let from_find_by = TestUser::find_by_sync(HashMap::from([("id".to_owned(), json!(2))]))
.expect("find_by_sync should succeed")
.expect("find_by_sync should return Bob");
assert_eq!(from_find, from_where);
assert_eq!(from_where, from_find_by);
});
}
ignored_rails_tests!(
"Rails-specific: Active Record finder overloads that accept strings, variadic id arrays, raw select SQL, and custom primary-key models are not implemented";
test_find_by_title_and_id_with_hash,
test_find_with_proc_parameter_and_block,
test_find_with_ids_returning_ordered,
test_find_with_ids_and_order_clause,
test_find_with_ids_with_limit_and_order_clause,
test_find_with_ids_and_limit,
test_find_with_ids_where_and_limit,
test_find_with_ids_and_offset,
test_find_with_ids_with_no_id_passed,
test_find_with_ids_with_id_out_of_range,
test_find_passing_active_record_object_is_not_permitted,
test_find_with_string,
test_find_by_array_of_one_id,
test_find_by_ids,
test_find_by_ids_with_limit_and_offset,
test_find_an_empty_array,
test_find_by_ids_missing_one,
test_find_with_entire_select_statement,
test_find_by_sql_with_sti_on_joined_table,
test_find_by_association_subquery,
test_find_with_hash_conditions_on_joined_table,
test_find_with_hash_conditions_on_joined_table_and_with_range,
test_find_on_hash_conditions_with_explicit_table_name_and_aggregate,
test_find_on_association_proxy_conditions,
test_hash_condition_find_with_aggregate_having_one_mapping,
test_hash_condition_find_with_aggregate_having_three_mappings_array,
test_hash_condition_find_with_aggregate_having_one_mapping_array,
test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate,
test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value,
test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value,
test_hash_condition_find_with_aggregate_having_three_mappings,
test_hash_condition_find_with_one_condition_being_aggregate_and_another_not,
test_hash_condition_find_nil_with_aggregate_having_one_mapping,
test_hash_condition_find_nil_with_aggregate_having_multiple_mappings,
test_hash_condition_find_empty_array_with_aggregate_having_multiple_mappings,
test_find_by_one_attribute_that_is_an_aggregate,
test_find_by_one_attribute_that_is_an_aggregate_with_one_attribute_difference,
test_find_by_two_attributes_that_are_both_aggregates,
test_find_by_two_attributes_with_one_being_an_aggregate,
test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct,
test_find_with_eager_loading_collection_and_ordering_by_collection_primary_key,
test_eager_load_for_no_has_many_with_limit_and_joins_for_has_many,
test_eager_load_for_no_has_many_with_limit_and_left_joins_for_has_many
);
#[test]
fn test_find_with_hash_parameter() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_sync(HashMap::from([("id".to_owned(), json!(1))]))
.expect("find_by_sync should succeed")
.expect("seeded id should resolve");
assert_eq!(user.name, "Alice");
assert_eq!(user.email, "alice@example.com");
});
}
#[test]
fn test_find_with_custom_select_excluding_id() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.select(&["name", "email"])
.order("id", OrderDirection::Asc),
)
.expect("first relation query should succeed")
.expect("custom projection should return a row");
assert_eq!(user.name, "Alice");
assert_eq!(user.email, "alice@example.com");
});
}
#[test]
fn test_find_only_some_columns() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.select(&["name"])
.order("id", OrderDirection::Asc),
)
.expect("first relation query should succeed")
.expect("projection should return a row");
assert_eq!(user.name, "Alice");
assert_eq!(user.email, "");
});
}
#[test]
fn test_find_one_message_on_primary_key() {
run_seeded_finder_test(|| {
let error = TestUser::find_sync(404).expect_err("missing id should return an error");
assert_eq!(error.to_string(), "record not found");
});
}
ignored_rails_tests!(
"Rails-specific: association joins, eager loading, polymorphic scopes, composite primary keys, and aggregate-valued predicates are not implemented for TestUser relations";
test_any_with_scope_on_hash_includes,
test_exists_with_polymorphic_relation,
test_exists_with_distinct_and_offset_and_joins,
test_exists_with_distinct_and_offset_and_eagerload_and_order,
test_exists_with_joins,
test_exists_with_left_joins,
test_exists_with_eager_load,
test_exists_with_includes_limit_and_empty_result,
test_exists_with_distinct_association_includes_and_limit,
test_exists_with_distinct_association_includes_limit_and_order,
test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association,
test_exists_with_aggregate_having_three_mappings,
test_exists_with_aggregate_having_three_mappings_with_one_difference,
test_include_on_unloaded_relation_with_having_referencing_aliased_select,
test_include_on_unloaded_relation_with_composite_primary_key,
test_include_on_loaded_relation_with_composite_primary_key,
test_member_on_unloaded_relation_with_composite_primary_key,
test_member_on_loaded_relation_with_composite_primary_key,
test_find_with_group_and_sanitized_having_method,
test_find_by_sql_with_sti_on_joined_table,
test_find_by_association_subquery,
test_find_with_hash_conditions_on_joined_table,
test_find_with_hash_conditions_on_joined_table_and_with_range,
test_find_on_hash_conditions_with_explicit_table_name_and_aggregate,
test_find_on_association_proxy_conditions,
test_hash_condition_find_with_aggregate_having_one_mapping,
test_hash_condition_find_with_aggregate_having_three_mappings_array,
test_hash_condition_find_with_aggregate_having_one_mapping_array,
test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate,
test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value,
test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value,
test_hash_condition_find_with_aggregate_having_three_mappings,
test_hash_condition_find_with_one_condition_being_aggregate_and_another_not,
test_hash_condition_find_nil_with_aggregate_having_one_mapping,
test_hash_condition_find_nil_with_aggregate_having_multiple_mappings,
test_hash_condition_find_empty_array_with_aggregate_having_multiple_mappings,
test_find_by_one_attribute_that_is_an_aggregate,
test_find_by_one_attribute_that_is_an_aggregate_with_one_attribute_difference,
test_find_by_two_attributes_that_are_both_aggregates,
test_find_by_two_attributes_with_one_being_an_aggregate,
test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct,
test_find_with_eager_loading_collection_and_ordering_by_collection_primary_key,
test_eager_load_for_no_has_many_with_limit_and_joins_for_has_many,
test_eager_load_for_no_has_many_with_limit_and_left_joins_for_has_many
);
#[test]
fn test_with_limiting_with_custom_select() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.select(&["email"])
.order("id", OrderDirection::Asc)
.limit(1),
)
.expect("first relation query should succeed")
.expect("limited projection should return a row");
assert_eq!(user.name, "");
assert_eq!(user.email, "alice@example.com");
});
}
#[test]
fn test_custom_select_takes_precedence_over_original_value() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.select(&["name"])
.reselect(&["email"])
.order("id", OrderDirection::Asc),
)
.expect("first relation query should succeed")
.expect("reselected relation should return a row");
assert_eq!(user.name, "");
assert_eq!(user.email, "alice@example.com");
});
}
ignored_rails_tests!(
"Rails-specific: these assertions depend on SQL instrumentation, allocation side effects, or query-count inspection that rustrails-record does not expose";
test_symbols_table_ref,
test_exists_does_not_select_columns_without_alias,
test_exists_does_not_instantiate_records,
test_nth_to_last_with_order_uses_limit,
test_take_and_first_and_last_with_integer_should_use_sql_limit,
test_last_with_integer_and_order_should_use_sql_limit,
test_last_with_integer_and_reorder_should_use_sql_limit,
test_last_on_loaded_relation_should_not_use_sql,
);
ignored_rails_tests!(
"Rails-specific: ordinal finder helpers, bang wrappers for first/last, and array-returning finder variants are not implemented by rustrails-record";
test_find_by_bang_on_relation_with_large_number,
test_first_bang_present,
test_first_bang_missing,
test_model_class_responds_to_first_bang,
test_second,
test_second_with_offset,
test_second_have_primary_key_order_by_default,
test_model_class_responds_to_second_bang,
test_third,
test_third_with_offset,
test_third_have_primary_key_order_by_default,
test_model_class_responds_to_third_bang,
test_fourth,
test_fourth_with_offset,
test_fourth_have_primary_key_order_by_default,
test_model_class_responds_to_fourth_bang,
test_fifth,
test_fifth_with_offset,
test_fifth_have_primary_key_order_by_default,
test_model_class_responds_to_fifth_bang,
test_second_to_last,
test_second_to_last_have_primary_key_order_by_default,
test_model_class_responds_to_second_to_last_bang,
test_third_to_last,
test_third_to_last_have_primary_key_order_by_default,
test_model_class_responds_to_third_to_last_bang,
test_last_bang_present,
test_last_bang_missing,
test_model_class_responds_to_last_bang,
test_last_with_integer_and_order_should_keep_the_order,
test_take_and_first_and_last_with_integer_should_return_an_array
);
#[test]
fn test_take() {
run_seeded_finder_test(|| {
let user = TestUser::take_sync()
.expect("take_sync should succeed")
.expect("seeded table should return a row");
assert_eq!(user.id, Some(1));
assert_eq!(user.name, "Alice");
});
}
#[test]
fn test_take_failing() {
run_empty_finder_test(|| {
assert!(
TestUser::take_sync()
.expect("take_sync should succeed")
.is_none()
);
});
}
#[test]
fn test_take_bang_present() {
run_seeded_finder_test(|| {
let user = TestUser::take_bang_sync().expect("take_bang_sync should succeed");
assert_eq!(user.id, Some(1));
assert_eq!(user.email, "alice@example.com");
});
}
#[test]
fn test_take_bang_missing() {
run_empty_finder_test(|| {
let error = TestUser::take_bang_sync().expect_err("empty table should fail");
assert!(matches!(error, crate::RecordError::NotFound));
});
}
#[test]
fn test_sole() {
run_empty_finder_test(|| {
insert_user("Solo", "solo@example.com");
let user = TestUser::sole_sync().expect("sole_sync should succeed");
assert_eq!(user.name, "Solo");
assert_eq!(user.email, "solo@example.com");
});
}
#[test]
fn test_sole_failing_none() {
run_empty_finder_test(|| {
let error = TestUser::sole_sync().expect_err("empty table should fail sole");
assert!(matches!(error, crate::RecordError::NotFound));
});
}
#[test]
fn test_sole_failing_many() {
run_seeded_finder_test(|| {
let error = TestUser::sole_sync().expect_err("multiple rows should fail sole");
assert!(matches!(error, crate::RecordError::SoleRecordExceeded));
});
}
#[test]
fn test_sole_record_exceeded_record_accessor() {
run_seeded_finder_test(|| {
let error = Relation::<TestUser>::new()
.sole_sync()
.expect_err("multiple rows should fail sole relation");
assert!(matches!(error, crate::RecordError::SoleRecordExceeded));
});
}
#[test]
fn test_unexisting_record_exception_handling() {
run_empty_finder_test(|| {
let error = TestUser::take_bang_sync().expect_err("empty table should fail");
assert_eq!(error.to_string(), "record not found");
});
}
ignored_rails_tests!(
"Rails-specific: implicit-order metadata, reversible custom ordering, and finder behavior for models without ordinary primary keys are not available on TestUser";
test_find_doesnt_have_implicit_ordering,
test_first_without_order_columns,
test_first_without_order_columns_and_raise_on_missing_required_finder_order_columns_disabled,
test_first_with_at_least_primary_key,
test_first_with_at_least_implict_order_column,
test_last_without_order_columns,
test_last_without_order_columns_and_raise_on_missing_required_finder_order_columns_disabled,
test_last_with_at_least_primary_key,
test_last_with_at_least_implict_order_column,
test_last_with_at_least_query_constraints,
test_last_with_irreversible_order_value,
test_first_have_determined_order_by_default,
test_implicit_order_column_is_configurable_with_a_single_value,
test_implicit_order_column_is_configurable_with_multiple_values,
test_ordering_does_not_append_primary_keys_or_query_constraints_if_passed_an_implicit_order_column_array_ending_in_nil,
test_implicit_order_set_to_primary_key,
test_implicit_order_for_model_without_primary_key,
test_implicit_order_column_reorders_query_constraints,
test_implicit_order_column_prepends_query_constraints,
);
ignored_rails_tests!(
"Rails-specific: raw SQL fragments, hashed table references, ranges, IN-list coercions, time interpolation, and bind-variable APIs are unsupported by rustrails-record relation filters";
test_find_on_array_conditions,
test_find_on_hash_conditions,
test_find_on_hash_conditions_with_qualified_attribute_dot_notation_string,
test_find_on_hash_conditions_with_qualified_attribute_dot_notation_symbol,
test_find_on_hash_conditions_with_hashed_table_name,
test_find_on_combined_explicit_and_hashed_table_names,
test_find_on_hash_conditions_with_range,
test_find_on_hash_conditions_with_end_exclusive_range,
test_find_on_hash_conditions_with_multiple_ranges,
test_find_on_hash_conditions_with_array_of_integers_and_ranges,
test_find_on_hash_conditions_with_array_of_ranges,
test_find_on_hash_conditions_with_open_ended_range,
test_find_on_hash_conditions_with_numeric_range_for_string,
test_find_on_multiple_hash_conditions,
test_condition_interpolation,
test_condition_array_interpolation,
test_condition_hash_interpolation,
test_hash_condition_find_malformed,
test_hash_condition_find_with_escaped_characters,
test_hash_condition_find_with_array,
test_hash_condition_find_with_nil,
test_condition_utc_time_interpolation_with_default_timezone_local,
test_hash_condition_utc_time_interpolation_with_default_timezone_local,
test_condition_local_time_interpolation_with_default_timezone_utc,
test_hash_condition_local_time_interpolation_with_default_timezone_utc,
test_bind_variables,
test_bind_variables_with_quotes,
test_named_bind_variables_with_quotes,
test_named_bind_variables,
test_find_with_bad_sql,
test_joins_dont_clobber_id,
test_joins_with_string_array,
test_find_by_id_with_conditions_with_or,
test_find_ignores_previously_inserted_record,
test_find_by_empty_in_condition,
test_find_by_records,
test_find_with_nil_inside_set_passed_for_one_attribute,
test_find_with_nil_inside_set_passed_for_attribute,
);
ignored_rails_tests!(
"Rails-specific: strong parameters, dynamic finders, aliases, arbitrary-object coercions, and mismatched-class checks are not supported by TestUser";
test_exists_with_string,
test_exists_with_strong_parameters,
test_exists_passing_active_record_object_is_not_permitted,
test_exists_returns_false_with_false_arg,
test_exists_with_loaded_relation_having_unsaved_records,
test_exists_with_loaded_relation_having_updated_owner_record,
test_include_when_non_AR_object_passed_on_unloaded_relation,
test_include_when_non_AR_object_passed_on_loaded_relation,
test_member_when_non_AR_object_passed_on_unloaded_relation,
test_member_when_non_AR_object_passed_on_loaded_relation,
test_include_on_unloaded_relation_with_mismatched_class,
test_member_on_unloaded_relation_with_mismatched_class,
test_find_by_on_attribute_that_is_a_reserved_word,
test_find_by_one_attribute_that_is_an_alias,
test_find_by_one_attribute_bang_with_blank_defined,
test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching,
test_find_by_with_alias,
test_find_by_one_attribute_with_several_options,
test_find_by_two_attributes_but_passing_only_one,
test_find_by_invalid_method_syntax
);
#[test]
fn test_find_by_one_attribute_bang() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_bang_sync(HashMap::from([(
"email".to_owned(),
json!("bob@example.com"),
)]))
.expect("find_by_bang_sync should succeed");
assert_eq!(user.id, Some(2));
assert_eq!(user.name, "Bob");
});
}
#[test]
fn test_find_by_one_attribute_with_conditions() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Bob"))]))
.order("id", OrderDirection::Asc),
)
.expect("relation query should succeed")
.expect("scoped relation should return a row");
assert_eq!(user.id, Some(2));
assert_eq!(user.email, "bob@example.com");
});
}
ignored_rails_tests!(
"Rails-specific: direct SQL execution helpers and count_by_sql are outside the rustrails-record test surface";
test_count_by_sql,
test_select_value,
test_select_values,
test_select_rows,
);
#[test]
fn test_find_by_id_with_hash() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_sync(HashMap::from([("id".to_owned(), json!(2))]))
.expect("find_by_sync should succeed")
.expect("seeded id should resolve");
assert_eq!(user.id, Some(2));
assert_eq!(user.name, "Bob");
});
}
#[test]
fn test_find_by_id_returns_matching_record_when_present() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_id_sync(2)
.expect("find_by_id_sync should succeed")
.expect("seeded id should resolve");
assert_eq!(user.name, "Bob");
assert_eq!(user.email, "bob@example.com");
});
}
#[test]
fn test_find_by_id_returns_none_for_missing_rows() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_id_sync(404).expect("find_by_id_sync should succeed");
assert!(user.is_none());
});
}
#[test]
fn test_find_by_id_returns_none_for_empty_table() {
run_empty_finder_test(|| {
let user = TestUser::find_by_id_sync(1).expect("find_by_id_sync should succeed");
assert!(user.is_none());
});
}
#[test]
fn test_find_by_with_hash_conditions_returns_the_first_matching_record() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_sync(HashMap::from([("id".to_owned(), json!(3))]))
.expect("find_by_sync should succeed")
.expect("seeded id should resolve");
assert_eq!(user.name, "Carol");
});
}
#[test]
fn test_find_by_returns_nil_if_the_record_is_missing() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_sync(HashMap::from([("id".to_owned(), json!(404))]))
.expect("find_by_sync should succeed");
assert!(user.is_none());
});
}
#[test]
fn test_find_by_one_missing_attribute() {
run_seeded_finder_test(|| {
let error = TestUser::find_by_sync(HashMap::from([("missing".to_owned(), json!(1))]))
.expect_err("unknown columns should be rejected");
assert!(
matches!(error, crate::RecordError::Invalid(message) if message.contains("missing"))
);
});
}
#[test]
fn test_find_by_nil_attribute() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_sync(HashMap::from([("name".to_owned(), Value::Null)]))
.expect("find_by_sync should succeed");
assert!(user.is_none());
});
}
#[test]
fn test_find_by_nil_and_not_nil_attributes() {
run_seeded_finder_test(|| {
let user = TestUser::find_by_sync(HashMap::from([
("name".to_owned(), Value::Null),
("email".to_owned(), json!("alice@example.com")),
]))
.expect("find_by_sync should succeed");
assert!(user.is_none());
});
}
#[test]
fn test_exists_with_limit_and_offset() {
run_seeded_finder_test(|| {
assert!(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(2)
.limit(1)
.exists_sync()
.expect("limited scoped exists should succeed")
);
assert!(
!Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(3)
.limit(1)
.exists_sync()
.expect("out-of-range limited exists should succeed")
);
});
}
#[test]
fn test_first_with_desc_order() {
run_seeded_finder_test(|| {
let user = first_relation(Relation::<TestUser>::new().order("id", OrderDirection::Desc))
.expect("ordered first query should succeed")
.expect("descending relation should have a first row");
assert_eq!(user.id, Some(3));
assert_eq!(user.name, "Carol");
});
}
#[test]
fn test_last_with_desc_order() {
run_seeded_finder_test(|| {
let user = last_relation(Relation::<TestUser>::new().order("id", OrderDirection::Desc))
.expect("ordered last query should succeed")
.expect("descending relation should have a last row");
assert_eq!(user.id, Some(1));
assert_eq!(user.name, "Alice");
});
}
#[test]
fn test_first_with_offset_only() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(1),
)
.expect("offset first query should succeed")
.expect("offset relation should have a first row");
assert_eq!(user.id, Some(2));
assert_eq!(user.name, "Bob");
});
}
#[test]
fn test_last_with_offset_only() {
run_seeded_finder_test(|| {
let user = last_relation(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(1),
)
.expect("offset last query should succeed")
.expect("offset relation should have a last row");
assert_eq!(user.id, Some(3));
assert_eq!(user.name, "Carol");
});
}
#[test]
fn test_find_by_on_relation_with_reorder() {
run_seeded_finder_test(|| {
insert_user("Bob", "bobby@example.com");
let user = first_relation(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Bob"))]))
.order("email", OrderDirection::Asc)
.reorder("email", OrderDirection::Desc),
)
.expect("reordered relation query should succeed")
.expect("reordered relation should return a row");
assert_eq!(user.email, "bobby@example.com");
});
}
#[test]
fn test_reselect_on_relation_replaces_selected_columns_for_first() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.select(&["name"])
.reselect(&["email"])
.order("id", OrderDirection::Asc),
)
.expect("reselected first query should succeed")
.expect("reselected relation should return a row");
assert_eq!(user.name, "");
assert_eq!(user.email, "alice@example.com");
});
}
#[test]
fn test_find_by_bang_returns_not_found_when_missing() {
run_empty_finder_test(|| {
let error = TestUser::find_by_bang_sync(HashMap::from([(
"email".to_owned(),
json!("missing@example.com"),
)]))
.expect_err("missing row should fail");
assert!(matches!(error, crate::RecordError::NotFound));
});
}
#[test]
fn test_find_sole_by_returns_matching_row() {
run_seeded_finder_test(|| {
let user =
TestUser::find_sole_by_sync(HashMap::from([("name".to_owned(), json!("Carol"))]))
.expect("single matching row should exist");
assert_eq!(user.email, "carol@example.com");
});
}
#[test]
fn test_find_sole_by_returns_not_found_for_empty_scope() {
run_seeded_finder_test(|| {
let error =
TestUser::find_sole_by_sync(HashMap::from([("name".to_owned(), json!("Nobody"))]))
.expect_err("missing rows should fail");
assert!(matches!(error, crate::RecordError::NotFound));
});
}
#[test]
fn test_find_sole_by_returns_exceeded_for_multiple_rows() {
run_seeded_finder_test(|| {
insert_user("Bob", "bobby@example.com");
let error = TestUser::find_sole_by_sync(HashMap::from([("name".to_owned(), json!("Bob"))]))
.expect_err("multiple matches should fail");
assert!(matches!(error, crate::RecordError::SoleRecordExceeded));
});
}
#[test]
fn test_take_sync_can_follow_relation_load() {
run_seeded_finder_test(|| {
let loaded = load_relation(Relation::<TestUser>::new()).expect("relation should load");
let user = TestUser::take_sync()
.expect("take_sync should succeed")
.expect("seeded table should return a row");
assert_eq!(loaded.len(), 3);
assert_eq!(user.name, "Alice");
});
}
#[test]
fn test_sole_sync_can_follow_relation_load() {
run_empty_finder_test(|| {
insert_user("Solo", "solo@example.com");
let loaded = load_relation(Relation::<TestUser>::new()).expect("relation should load");
let user = Relation::<TestUser>::new()
.sole_sync()
.expect("sole_sync should succeed");
assert_eq!(loaded.len(), 1);
assert_eq!(user.email, "solo@example.com");
});
}
#[test]
fn test_find_only_selected_email_returns_default_name() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.select(&["email"])
.order("id", OrderDirection::Asc),
)
.expect("first relation query should succeed")
.expect("projection should return a row");
assert_eq!(user.name, "");
assert_eq!(user.email, "alice@example.com");
});
}
#[test]
fn test_find_only_selected_name_returns_default_email() {
run_seeded_finder_test(|| {
let user = first_relation(
Relation::<TestUser>::new()
.select(&["name"])
.order("id", OrderDirection::Asc),
)
.expect("first relation query should succeed")
.expect("projection should return a row");
assert_eq!(user.name, "Alice");
assert_eq!(user.email, "");
});
}
#[cfg(test)]
mod wave3_finder_sanitization_ports {
use crate::sanitization::{sanitize_sql, sanitize_sql_like};
use serde_json::{Value, json};
macro_rules! sanitize_sql_case {
($name:ident, $template:expr, [$($bind:expr),* $(,)?], $expected:expr) => {
#[test]
fn $name() {
let binds = vec![$($bind),*];
assert_eq!(sanitize_sql($template, &binds), $expected);
}
};
}
sanitize_sql_case!(sanitize_sql_string_literal_case, "value = ?", [json!("hello")], "value = 'hello'");
sanitize_sql_case!(sanitize_sql_empty_string_case, "value = ?", [json!("")], "value = ''");
sanitize_sql_case!(sanitize_sql_apostrophe_case, "name = ?", [json!("O'Brien")], "name = 'O''Brien'");
sanitize_sql_case!(sanitize_sql_multiline_case, "value = ?", [json!("line1\nline2")], "value = 'line1\nline2'");
sanitize_sql_case!(sanitize_sql_percent_case, "pattern = ?", [json!("100%")], "pattern = '100%'");
sanitize_sql_case!(sanitize_sql_underscore_case, "pattern = ?", [json!("snake_case")], "pattern = 'snake_case'");
sanitize_sql_case!(sanitize_sql_backslash_case, "path = ?", [json!("C:\\Temp")], "path = 'C:\\Temp'");
sanitize_sql_case!(sanitize_sql_unicode_case, "name = ?", [json!("Здравствуйте")], "name = 'Здравствуйте'");
sanitize_sql_case!(sanitize_sql_emoji_case, "emoji = ?", [json!("😀")], "emoji = '😀'");
sanitize_sql_case!(sanitize_sql_integer_case, "id = ?", [json!(42)], "id = 42");
sanitize_sql_case!(sanitize_sql_negative_integer_case, "id = ?", [json!(-7)], "id = -7");
sanitize_sql_case!(sanitize_sql_zero_case, "id = ?", [json!(0)], "id = 0");
sanitize_sql_case!(sanitize_sql_float_case, "score = ?", [json!(3.5)], "score = 3.5");
sanitize_sql_case!(sanitize_sql_negative_float_case, "score = ?", [json!(-2.25)], "score = -2.25");
sanitize_sql_case!(sanitize_sql_true_case, "flag = ?", [json!(true)], "flag = TRUE");
sanitize_sql_case!(sanitize_sql_false_case, "flag = ?", [json!(false)], "flag = FALSE");
sanitize_sql_case!(sanitize_sql_null_case, "deleted_at = ?", [Value::Null], "deleted_at = NULL");
sanitize_sql_case!(sanitize_sql_object_case, "meta = ?", [json!({"role": "admin"})], "meta = '{\"role\":\"admin\"}'");
sanitize_sql_case!(sanitize_sql_array_case, "ids = ?", [json!([1, 2, 3])], "ids = '[1,2,3]'");
sanitize_sql_case!(sanitize_sql_injection_case, "name = ?", [json!("Robert'); DROP TABLE users;--")], "name = 'Robert''); DROP TABLE users;--'");
sanitize_sql_case!(sanitize_sql_missing_bind_keeps_placeholder, "name = ? AND email = ?", [json!("Alice")], "name = 'Alice' AND email = ?");
sanitize_sql_case!(sanitize_sql_extra_bind_is_ignored, "id = ?", [json!(1), json!(2)], "id = 1");
sanitize_sql_case!(sanitize_sql_adjacent_placeholders_join_literals, "??", [json!(1), json!(2)], "12");
sanitize_sql_case!(sanitize_sql_placeholder_at_start, "? = id", [json!(9)], "9 = id");
sanitize_sql_case!(sanitize_sql_placeholder_at_end, "id = ?", [json!(9)], "id = 9");
sanitize_sql_case!(sanitize_sql_parenthesized_placeholder, "id IN (?)", [json!([1, 2])], "id IN ('[1,2]')");
sanitize_sql_case!(sanitize_sql_without_placeholders_returns_template, "SELECT 1", [json!(1)], "SELECT 1");
sanitize_sql_case!(sanitize_sql_mixed_types_preserve_order, "a = ? AND b = ? AND c = ?", [json!("Alice"), json!(false), json!(3)], "a = 'Alice' AND b = FALSE AND c = 3");
sanitize_sql_case!(sanitize_sql_null_in_middle_of_template, "a = ? OR b IS ?", [json!(1), Value::Null], "a = 1 OR b IS NULL");
sanitize_sql_case!(sanitize_sql_bool_and_number_template, "active = ? AND attempts < ?", [json!(true), json!(5)], "active = TRUE AND attempts < 5");
sanitize_sql_case!(sanitize_sql_all_placeholders_preserved_without_binds, "? ? ?", [], "? ? ?");
sanitize_sql_case!(sanitize_sql_two_placeholders_one_bind, "? = ?", [json!("lhs")], "'lhs' = ?");
sanitize_sql_case!(sanitize_sql_three_binds_two_placeholders, "? + ?", [json!(1), json!(2), json!(3)], "1 + 2");
sanitize_sql_case!(sanitize_sql_object_and_array_binds, "meta = ? AND ids = ?", [json!({"ok": true}), json!([4, 5])], "meta = '{\"ok\":true}' AND ids = '[4,5]'");
sanitize_sql_case!(sanitize_sql_spaces_are_preserved, " name = ? ", [json!("Alice")], " name = 'Alice' ");
sanitize_sql_case!(sanitize_sql_newlines_are_preserved, "name = ?\nAND id = ?", [json!("Alice"), json!(1)], "name = 'Alice'\nAND id = 1");
sanitize_sql_case!(sanitize_sql_quotes_in_template_are_unchanged, "name = '?' AND id = ?", [json!(1)], "name = '1' AND id = ?");
sanitize_sql_case!(sanitize_sql_like_pattern_template, "title LIKE ?", [json!("20%")], "title LIKE '20%'");
sanitize_sql_case!(sanitize_sql_empty_template, "", [json!(1)], "");
macro_rules! sanitize_sql_like_case {
($name:ident, $input:expr, $expected:expr) => {
#[test]
fn $name() {
assert_eq!(sanitize_sql_like($input), $expected);
}
};
}
sanitize_sql_like_case!(sanitize_sql_like_percent_only, "%", "\\%");
sanitize_sql_like_case!(sanitize_sql_like_underscore_only, "_", "\\_");
sanitize_sql_like_case!(sanitize_sql_like_backslash_only, "\\", "\\\\");
sanitize_sql_like_case!(sanitize_sql_like_percent_underscore_combo, "%_", "\\%\\_");
sanitize_sql_like_case!(sanitize_sql_like_safe_text_is_unchanged, "plain-text", "plain-text");
sanitize_sql_like_case!(sanitize_sql_like_windows_path, "C:\\Programs\\MsPaint", "C:\\\\Programs\\\\MsPaint");
sanitize_sql_like_case!(sanitize_sql_like_mixed_wildcards_and_slashes, "100%_done\\today", "100\\%\\_done\\\\today");
sanitize_sql_like_case!(sanitize_sql_like_unicode_text_is_unchanged, "Здравствуйте", "Здравствуйте");
sanitize_sql_like_case!(sanitize_sql_like_emoji_text_is_unchanged, "😀", "😀");
sanitize_sql_like_case!(sanitize_sql_like_repeated_percent, "%%%", "\\%\\%\\%");
sanitize_sql_like_case!(sanitize_sql_like_repeated_underscore, "__", "\\_\\_");
sanitize_sql_like_case!(sanitize_sql_like_alternating_wildcards, "_%%_", "\\_\\%\\%\\_");
sanitize_sql_like_case!(sanitize_sql_like_trailing_backslash, "path\\", "path\\\\");
sanitize_sql_like_case!(sanitize_sql_like_leading_backslash, "\\path", "\\\\path");
sanitize_sql_like_case!(sanitize_sql_like_spaces_are_preserved, "hello world", "hello world");
sanitize_sql_like_case!(sanitize_sql_like_punctuation_is_preserved, "hello-world!", "hello-world!");
sanitize_sql_like_case!(sanitize_sql_like_percent_at_both_ends, "%name%", "\\%name\\%");
sanitize_sql_like_case!(sanitize_sql_like_snake_case_name, "snake_case_name", "snake\\_case\\_name");
sanitize_sql_like_case!(sanitize_sql_like_empty_string, "", "");
sanitize_sql_like_case!(sanitize_sql_like_numbers_only, "12345", "12345");
}