use std::collections::HashMap;
use rustrails_support::{database, ignored_rails_test, runtime};
use sea_orm::{ActiveModelTrait, ActiveValue::Set, ConnectionTrait, Schema};
use serde_json::json;
use crate::base::test_support::{TestUser, seed_users, test_user};
use crate::{OrderDirection, Relation};
fn run_relation_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()
.unwrap();
}
fn run_seeded_relation_test(test: impl FnOnce() + Send + 'static) {
run_relation_test(true, test);
}
fn load_relation(relation: Relation<TestUser>) -> Vec<TestUser> {
relation.load_sync().expect("relation should load")
}
fn insert_user(name: &str, email: &str) {
database::with_db(|db| {
runtime::block_on(async {
test_user::ActiveModel {
name: Set(name.to_owned()),
email: Set(email.to_owned()),
..Default::default()
}
.insert(db)
.await
.expect("fixture insert should succeed");
});
});
}
fn relation_names(relation: Relation<TestUser>) -> Vec<String> {
load_relation(relation)
.into_iter()
.map(|user| user.name)
.collect()
}
#[test]
fn test_construction() {
run_seeded_relation_test(|| {
let users = load_relation(Relation::<TestUser>::new());
assert_eq!(users.len(), 3);
assert_eq!(users[0].name, "Alice");
assert!(
users
.iter()
.all(|user| user.state == crate::RecordState::Persisted)
);
});
}
ignored_rails_test!(
test_responds_to_model_and_returns_klass,
"Rails-specific: Relation::model/klass reflection APIs are not implemented"
);
#[test]
fn test_initialize_single_values() {
run_seeded_relation_test(|| {
let relation = Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(1)
.limit(1);
let first = relation
.first_sync()
.expect("query should succeed")
.expect("row should exist");
let last = relation
.last_sync()
.expect("query should succeed")
.expect("row should exist");
assert_eq!(first.name, "Bob");
assert_eq!(last.name, "Bob");
});
}
#[test]
fn test_multi_value_initialize() {
run_seeded_relation_test(|| {
insert_user("Bob", "bobby@example.com");
let users = load_relation(
Relation::<TestUser>::new()
.order("name", OrderDirection::Asc)
.order("email", OrderDirection::Desc),
);
let ordered_pairs = users
.into_iter()
.map(|user| (user.name, user.email))
.collect::<Vec<_>>();
assert_eq!(
ordered_pairs,
vec![
("Alice".to_owned(), "alice@example.com".to_owned()),
("Bob".to_owned(), "bobby@example.com".to_owned()),
("Bob".to_owned(), "bob@example.com".to_owned()),
("Carol".to_owned(), "carol@example.com".to_owned()),
]
);
});
}
#[test]
fn test_multi_values_deduplication_with_merge() {
run_seeded_relation_test(|| {
insert_user("Bob", "bobby@example.com");
let names = Relation::<TestUser>::new()
.select(&["name"])
.distinct()
.order("name", OrderDirection::Asc)
.pluck_sync("name")
.expect("pluck should succeed");
assert_eq!(names, vec![json!("Alice"), json!("Bob"), json!("Carol")]);
});
}
#[test]
fn test_extensions() {
run_seeded_relation_test(|| {
insert_user("Bob", "bobby@example.com");
let group_count = Relation::<TestUser>::new()
.group("name")
.having("COUNT(*) > 1")
.count_sync()
.expect("count should succeed");
assert_eq!(group_count, 1);
});
}
#[test]
fn test_empty_where_values_hash() {
run_seeded_relation_test(|| {
let baseline = relation_names(Relation::<TestUser>::new().order("id", OrderDirection::Asc));
let filtered = relation_names(
Relation::<TestUser>::new()
.r#where(HashMap::new())
.order("id", OrderDirection::Asc),
);
assert_eq!(filtered, baseline);
});
}
ignored_rails_test!(
test_where_values_hash_with_in_clause,
"Rails-specific: array-valued IN predicates are not supported by current Relation::where"
);
#[test]
fn test_has_values() {
run_seeded_relation_test(|| {
insert_user("Bob", "bobby@example.com");
let users = load_relation(
Relation::<TestUser>::new()
.r#where(HashMap::from([("name".to_owned(), json!("Bob"))]))
.not(HashMap::from([(
"email".to_owned(),
json!("bobby@example.com"),
)]))
.order("id", OrderDirection::Asc),
);
assert_eq!(users.len(), 1);
assert_eq!(users[0].email, "bob@example.com");
});
}
#[test]
fn test_values_wrong_table() {
run_seeded_relation_test(|| {
let error = Relation::<TestUser>::new()
.r#where(HashMap::from([("missing".to_owned(), json!(1))]))
.load_sync()
.expect_err("unknown column should fail");
assert!(
matches!(error, crate::RecordError::Invalid(message) if message.contains("missing"))
);
});
}
ignored_rails_test!(
test_tree_is_not_traversed,
"Rails-specific: Arel tree traversal behavior does not exist in the current Relation API"
);
ignored_rails_test!(
test_scope_for_create,
"Rails-specific: scope_for_create is not implemented for rustrails-record Relation"
);
ignored_rails_test!(
test_create_with_value,
"Rails-specific: create-scoped relation helpers are not implemented in these relation tests"
);
ignored_rails_test!(
test_create_with_value_with_wheres,
"Rails-specific: create-scoped relation helpers are not implemented in these relation tests"
);
#[test]
fn test_empty_scope() {
run_seeded_relation_test(|| {
let relation = Relation::<TestUser>::new().order("id", OrderDirection::Asc);
assert_eq!(relation.count_sync().expect("count should succeed"), 3);
assert!(relation.exists_sync().expect("exists should succeed"));
assert_eq!(
relation
.first_sync()
.expect("first should succeed")
.expect("row should exist")
.name,
"Alice"
);
assert_eq!(
relation
.last_sync()
.expect("last should succeed")
.expect("row should exist")
.name,
"Carol"
);
assert!(
!relation
.explain_sync()
.expect("explain should succeed")
.trim()
.is_empty()
);
});
}
ignored_rails_test!(
test_bad_constants_raise_errors,
"Rails-specific: constant resolution errors are not part of the current Relation API"
);
ignored_rails_test!(
test_empty_eager_loading_predicate,
"Rails-specific: eager loading state is not observable through the current Relation API"
);
ignored_rails_test!(
test_eager_load_values,
"Rails-specific: eager_load value inspection is not implemented"
);
ignored_rails_test!(
test_references_values,
"Rails-specific: references value inspection is not implemented"
);
ignored_rails_test!(
test_references_values_dont_duplicate,
"Rails-specific: references de-duplication is not implemented"
);
ignored_rails_test!(
test_merging_readonly_false,
"Rails-specific: merge semantics and readonly propagation are not implemented"
);
ignored_rails_test!(
test_relation_merging_with_merged_joins_as_symbols,
"Rails-specific: relation merge joins are not implemented"
);
ignored_rails_test!(
test_relation_merging_with_merged_symbol_joins_keeps_inner_joins,
"Rails-specific: relation merge joins are not implemented"
);
ignored_rails_test!(
test_relation_merging_with_merged_symbol_joins_has_correct_size_and_count,
"Rails-specific: relation merge joins are not implemented"
);
ignored_rails_test!(
test_relation_merging_with_merged_symbol_joins_is_aliased,
"Rails-specific: relation merge joins are not implemented"
);
ignored_rails_test!(
test_relation_with_merged_joins_aliased_works,
"Rails-specific: joined alias resolution is not implemented"
);
ignored_rails_test!(
test_relation_merging_with_joins_as_join_dependency_pick_proper_parent,
"Rails-specific: join dependency merging is not implemented"
);
ignored_rails_test!(
test_merge_raises_with_invalid_argument,
"Rails-specific: Relation::merge is not implemented"
);
#[test]
fn test_respond_to_for_non_selected_element() {
run_seeded_relation_test(|| {
let users = load_relation(
Relation::<TestUser>::new()
.select(&["name"])
.order("id", OrderDirection::Asc),
);
assert_eq!(users[0].name, "Alice");
assert_eq!(users[0].id, Some(0));
assert_eq!(users[0].email, "");
});
}
#[test]
fn test_select_quotes_when_using_from_clause() {
run_seeded_relation_test(|| {
let users = load_relation(
Relation::<TestUser>::new()
.select(&["name"])
.reselect(&["email"])
.order("id", OrderDirection::Asc),
);
assert_eq!(users[0].name, "");
assert_eq!(users[0].email, "alice@example.com");
});
}
ignored_rails_test!(
test_selecting_aliased_attribute_quotes_column_name_when_from_is_used,
"Rails-specific: aliased select expressions with custom FROM clauses are not implemented"
);
ignored_rails_test!(
test_relation_merging_with_merged_joins_as_strings,
"Rails-specific: string join merges are not implemented"
);
ignored_rails_test!(
test_relation_merging_keeps_joining_order,
"Rails-specific: join merge ordering is not implemented"
);
ignored_rails_test!(
test_relation_with_annotation_includes_comment_in_to_sql,
"Rails-specific: SQL annotations/comments are not implemented"
);
ignored_rails_test!(
test_relation_with_annotation_includes_comment_in_sql,
"Rails-specific: SQL annotations/comments are not implemented"
);
ignored_rails_test!(
test_relation_with_annotation_chains_sql_comments,
"Rails-specific: SQL annotations/comments are not implemented"
);
ignored_rails_test!(
test_relation_with_annotation_filters_sql_comment_delimiters,
"Rails-specific: SQL annotations/comments are not implemented"
);
ignored_rails_test!(
test_relation_with_annotation_includes_comment_in_count_query,
"Rails-specific: SQL annotations/comments are not implemented"
);
ignored_rails_test!(
test_relation_with_annotation_includes_comment_in_update_all_query,
"Rails-specific: SQL annotations/comments are not implemented"
);
ignored_rails_test!(
test_relation_with_annotation_includes_comment_in_delete_all_query,
"Rails-specific: SQL annotations/comments are not implemented"
);
ignored_rails_test!(
test_relation_without_annotation_does_not_include_an_empty_comment,
"Rails-specific: SQL annotations/comments are not implemented"
);
ignored_rails_test!(
test_relation_with_optimizer_hints_filters_sql_comment_delimiters,
"Rails-specific: optimizer hints are not implemented"
);
ignored_rails_test!(
test_does_not_duplicate_optimizer_hints_on_merge,
"Rails-specific: optimizer hints and merge semantics are not implemented"
);
ignored_rails_test!(
test_update_all_goes_through_normal_type_casting,
"Rails-specific: update_all relation semantics are not covered by current Relation API"
);
ignored_rails_test!(
test_skip_preloading_after_arel_has_been_generated,
"Rails-specific: Arel/preloading internals are not implemented"
);
fn relation_ids(relation: Relation<TestUser>) -> Vec<i64> {
load_relation(relation)
.into_iter()
.map(|user| user.id.expect("persisted rows should have ids"))
.collect()
}
fn relation_name_email_pairs(relation: Relation<TestUser>) -> Vec<(String, String)> {
load_relation(relation)
.into_iter()
.map(|user| (user.name, user.email))
.collect()
}
#[test]
fn test_finding_with_order() {
run_seeded_relation_test(|| {
let names = relation_names(Relation::<TestUser>::new().order("id", OrderDirection::Desc));
assert_eq!(names, vec!["Carol", "Bob", "Alice"]);
});
}
#[test]
fn test_finding_with_desc_order_with_string() {
run_seeded_relation_test(|| {
let names = relation_names(Relation::<TestUser>::new().order("name", OrderDirection::Desc));
assert_eq!(names, vec!["Carol", "Bob", "Alice"]);
});
}
#[test]
fn test_finding_with_asc_order_with_string() {
run_seeded_relation_test(|| {
let names = relation_names(Relation::<TestUser>::new().order("name", OrderDirection::Asc));
assert_eq!(names, vec!["Alice", "Bob", "Carol"]);
});
}
#[test]
fn test_finding_with_order_concatenated() {
run_seeded_relation_test(|| {
insert_user("Bob", "bobby@example.com");
let pairs = relation_name_email_pairs(
Relation::<TestUser>::new()
.order("name", OrderDirection::Asc)
.order("email", OrderDirection::Desc),
);
assert_eq!(
pairs,
vec![
("Alice".to_owned(), "alice@example.com".to_owned()),
("Bob".to_owned(), "bobby@example.com".to_owned()),
("Bob".to_owned(), "bob@example.com".to_owned()),
("Carol".to_owned(), "carol@example.com".to_owned()),
]
);
});
}
#[test]
fn test_finding_with_reorder() {
run_seeded_relation_test(|| {
let ids = relation_ids(
Relation::<TestUser>::new()
.order("name", OrderDirection::Asc)
.order("email", OrderDirection::Asc)
.reorder("id", OrderDirection::Desc),
);
assert_eq!(ids, vec![3, 2, 1]);
});
}
#[test]
fn test_finding_with_order_limit_and_offset() {
run_seeded_relation_test(|| {
let names = relation_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.limit(2)
.offset(1),
);
assert_eq!(names, vec!["Bob", "Carol"]);
});
}
#[test]
fn test_limit_with_zero_value_returns_no_records() {
run_seeded_relation_test(|| {
let names = relation_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.limit(0),
);
assert!(names.is_empty());
});
}
#[test]
fn test_offset_beyond_row_count_returns_no_records() {
run_seeded_relation_test(|| {
let names = relation_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(10),
);
assert!(names.is_empty());
});
}
#[test]
fn test_offset_without_limit_returns_remaining_records() {
run_seeded_relation_test(|| {
let names = relation_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(1),
);
assert_eq!(names, vec!["Bob", "Carol"]);
});
}
#[test]
fn test_limit_with_desc_order_returns_expected_records() {
run_seeded_relation_test(|| {
let names = relation_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Desc)
.limit(2),
);
assert_eq!(names, vec!["Carol", "Bob"]);
});
}
#[test]
fn test_reorder_with_first() {
run_seeded_relation_test(|| {
let first = Relation::<TestUser>::new()
.order("name", OrderDirection::Asc)
.reorder("id", OrderDirection::Desc)
.first_sync()
.expect("reordered first query should succeed")
.expect("reordered relation should return a row");
assert_eq!(first.id, Some(3));
assert_eq!(first.name, "Carol");
});
}
#[test]
fn test_reorder_preserves_limit_and_offset() {
run_seeded_relation_test(|| {
let names = relation_names(
Relation::<TestUser>::new()
.order("name", OrderDirection::Asc)
.offset(1)
.limit(2)
.reorder("id", OrderDirection::Desc),
);
assert_eq!(names, vec!["Bob", "Alice"]);
});
}
#[test]
fn test_reselect_replaces_previous_selection_with_multiple_columns() {
run_seeded_relation_test(|| {
let users = load_relation(
Relation::<TestUser>::new()
.select(&["name"])
.reselect(&["id", "email"])
.order("id", OrderDirection::Asc),
);
assert_eq!(users[0].id, Some(1));
assert_eq!(users[0].name, "");
assert_eq!(users[0].email, "alice@example.com");
});
}
#[test]
fn test_reselect_can_be_followed_by_order_and_limit() {
run_seeded_relation_test(|| {
let users = load_relation(
Relation::<TestUser>::new()
.select(&["name"])
.reselect(&["email"])
.order("id", OrderDirection::Desc)
.limit(1),
);
assert_eq!(users.len(), 1);
assert_eq!(users[0].name, "");
assert_eq!(users[0].email, "carol@example.com");
});
}
#[test]
fn test_reselect_with_distinct_can_pluck_the_reselected_column() {
run_seeded_relation_test(|| {
insert_user("Bob", "bobby@example.com");
let emails = Relation::<TestUser>::new()
.select(&["name"])
.distinct()
.reselect(&["email"])
.order("email", OrderDirection::Asc)
.pluck_sync("email")
.expect("pluck should succeed");
assert_eq!(
emails,
vec![
json!("alice@example.com"),
json!("bob@example.com"),
json!("bobby@example.com"),
json!("carol@example.com"),
]
);
});
}
#[test]
fn test_empty_scope_exists_on_empty_table() {
run_relation_test(false, || {
assert!(
!Relation::<TestUser>::new()
.exists_sync()
.expect("empty-table exists should succeed")
);
});
}
ignored_rails_test!(
test_reverse_order,
"Rails-specific: reverse_order is not implemented on rustrails-record Relation"
);
ignored_rails_test!(
test_reverse_order_with_arel_attribute,
"Rails-specific: reverse_order is not implemented on rustrails-record Relation"
);
ignored_rails_test!(
test_reverse_order_with_arel_attribute_as_hash,
"Rails-specific: reverse_order is not implemented on rustrails-record Relation"
);
ignored_rails_test!(
test_reverse_order_with_arel_node_as_hash,
"Rails-specific: reverse_order is not implemented on rustrails-record Relation"
);
ignored_rails_test!(
test_reverse_order_with_multiple_arel_attributes,
"Rails-specific: reverse_order is not implemented on rustrails-record Relation"
);
ignored_rails_test!(
test_reverse_order_with_arel_attributes_and_strings,
"Rails-specific: reverse_order is not implemented on rustrails-record Relation"
);
ignored_rails_test!(
test_double_reverse_order_produces_original_order,
"Rails-specific: reverse_order is not implemented on rustrails-record Relation"
);
ignored_rails_test!(
test_finding_with_order_by_aliased_attributes,
"Rails-specific: aliased attribute ordering is not implemented for TestUser relations"
);
ignored_rails_test!(
test_finding_with_assoc_order_by_aliased_attributes,
"Rails-specific: hash-style aliased ordering is not implemented for TestUser relations"
);
ignored_rails_test!(
test_finding_with_reorder_by_aliased_attributes,
"Rails-specific: aliased attribute reordering is not implemented for TestUser relations"
);
ignored_rails_test!(
test_finding_with_assoc_reorder_by_aliased_attributes,
"Rails-specific: hash-style aliased reordering is not implemented for TestUser relations"
);
ignored_rails_test!(
test_order_with_reorder_nil_removes_the_order,
"Rails-specific: reorder(nil) is not implemented because rustrails-record reorder requires an explicit column"
);
ignored_rails_test!(
test_reverse_order_with_reorder_nil_removes_the_order,
"Rails-specific: reorder(nil) and reverse_order are not implemented on rustrails-record Relation"
);
ignored_rails_test!(
test_reorder_with_take,
"Rails-specific: take is not implemented on rustrails-record Relation"
);