use backbone_orm::{
Entity, PaginationParams, SortParams, SortDirection, FilterParams, FilterCondition,
PaginatedResult, PaginationInfo, PostgresRepository, DatabaseOperations,
VERSION,
};
use sqlx::{PgPool, FromRow, postgres::PgRow};
use serde::{Serialize, Deserialize};
use chrono::NaiveDateTime;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
struct TestUser {
id: Uuid,
username: String,
email: String,
created_at: NaiveDateTime,
updated_at: NaiveDateTime,
deleted_at: Option<NaiveDateTime>,
}
impl Entity for TestUser {
fn id(&self) -> Option<&str> {
Some("test_id")
}
fn table_name() -> &'static str where Self: Sized {
"test_users"
}
fn is_deleted(&self) -> bool {
self.deleted_at.is_some()
}
fn created_at(&self) -> Option<NaiveDateTime> {
Some(self.created_at)
}
fn updated_at(&self) -> Option<NaiveDateTime> {
Some(self.updated_at)
}
}
impl TestUser {
fn new(username: &str, email: &str) -> Self {
let now = chrono::Utc::now().naive_utc();
Self {
id: Uuid::new_v4(),
username: username.to_string(),
email: email.to_string(),
created_at: now,
updated_at: now,
deleted_at: None,
}
}
fn with_deleted(mut self, deleted: bool) -> Self {
if deleted {
self.deleted_at = Some(chrono::Utc::now().naive_utc());
}
self
}
}
#[test]
fn test_pagination_params() {
let params = PaginationParams::new(1, 10);
assert_eq!(params.page, 1);
assert_eq!(params.per_page, 10);
assert_eq!(params.offset(), 0);
assert_eq!(params.limit(), 10);
let params = PaginationParams::new(0, 0);
assert_eq!(params.page, 1); assert_eq!(params.per_page, 1);
let params = PaginationParams::new(1, 150);
assert_eq!(params.per_page, 100);
let params = PaginationParams::new(3, 20);
assert_eq!(params.offset(), 40); }
#[test]
fn test_sort_params() {
let sort = SortParams {
field: "name".to_string(),
direction: SortDirection::Asc,
};
assert_eq!(sort.field, "name");
assert!(matches!(sort.direction, SortDirection::Asc));
let sort_desc = SortParams {
field: "created_at".to_string(),
direction: SortDirection::Desc,
};
assert_eq!(sort_desc.field, "created_at");
assert!(matches!(sort_desc.direction, SortDirection::Desc));
let default_sort = SortParams::default();
assert!(matches!(default_sort.direction, SortDirection::Asc));
}
#[test]
fn test_filter_params() {
let mut filters = FilterParams::default();
filters.conditions.insert(
"name".to_string(),
FilterCondition::Equals("John".to_string())
);
filters.conditions.insert(
"age".to_string(),
FilterCondition::GreaterThan("25".to_string())
);
filters.conditions.insert(
"status".to_string(),
FilterCondition::In(vec!["active".to_string(), "pending".to_string()])
);
if let Some(FilterCondition::Equals(value)) = filters.conditions.get("name") {
assert_eq!(value, "John");
} else {
panic!("Expected Equals condition");
}
if let Some(FilterCondition::GreaterThan(value)) = filters.conditions.get("age") {
assert_eq!(value, "25");
} else {
panic!("Expected GreaterThan condition");
}
if let Some(FilterCondition::In(values)) = filters.conditions.get("status") {
assert_eq!(values.len(), 2);
assert!(values.contains(&"active".to_string()));
} else {
panic!("Expected In condition");
}
}
#[test]
fn test_filter_condition_variants() {
let conditions = vec![
FilterCondition::Equals("test".to_string()),
FilterCondition::NotEquals("test".to_string()),
FilterCondition::GreaterThan("100".to_string()),
FilterCondition::LessThan("200".to_string()),
FilterCondition::Like("%test%".to_string()),
FilterCondition::In(vec!["a".to_string(), "b".to_string()]),
FilterCondition::IsNull,
FilterCondition::IsNotNull,
];
for (i, condition) in conditions.into_iter().enumerate() {
match condition {
FilterCondition::Equals(_) => assert_eq!(i, 0),
FilterCondition::NotEquals(_) => assert_eq!(i, 1),
FilterCondition::GreaterThan(_) => assert_eq!(i, 2),
FilterCondition::LessThan(_) => assert_eq!(i, 3),
FilterCondition::Like(_) => assert_eq!(i, 4),
FilterCondition::In(_) => assert_eq!(i, 5),
FilterCondition::IsNull => assert_eq!(i, 6),
FilterCondition::IsNotNull => assert_eq!(i, 7),
}
}
}
#[test]
fn test_pagination_info() {
let info = PaginationInfo::new(1, 10, 95);
assert_eq!(info.page, 1);
assert_eq!(info.per_page, 10);
assert_eq!(info.total, 95);
assert_eq!(info.total_pages, 10);
let info = PaginationInfo::new(1, 10, 100);
assert_eq!(info.total_pages, 10);
let info = PaginationInfo::new(1, 10, 5);
assert_eq!(info.total_pages, 1);
let info = PaginationInfo::new(1, 10, 0);
assert_eq!(info.total_pages, 0); }
#[test]
fn test_paginated_result() {
let data = vec![
TestUser::new("user1", "user1@example.com"),
TestUser::new("user2", "user2@example.com"),
];
let pagination = PaginationInfo::new(1, 10, 50);
let result = PaginatedResult {
data: data.clone(),
pagination: pagination.clone(),
};
assert_eq!(result.data.len(), 2);
assert_eq!(result.pagination.page, 1);
assert_eq!(result.pagination.total, 50);
assert_eq!(result.pagination.total_pages, 5);
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("data"));
assert!(json.contains("pagination"));
let deserialized: PaginatedResult<TestUser> = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.data.len(), 2);
assert_eq!(deserialized.pagination.page, 1);
}
#[test]
fn test_entity_trait() {
let user = TestUser::new("testuser", "test@example.com");
assert!(user.id().is_some());
assert_eq!(TestUser::table_name(), "test_users");
assert!(!user.is_deleted());
assert!(user.created_at().is_some());
assert!(user.updated_at().is_some());
if let Some(id_str) = user.id() {
assert!(!id_str.is_empty());
} else {
panic!("Expected ID to be present");
}
let deleted_user = user.with_deleted(true);
assert!(deleted_user.is_deleted());
assert!(deleted_user.deleted_at.is_some());
}
#[test]
fn test_entity_serialization() {
let user = TestUser::new("testuser", "test@example.com");
let json = serde_json::to_string(&user).unwrap();
assert!(json.contains("testuser"));
assert!(json.contains("test@example.com"));
let deserialized: TestUser = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.username, user.username);
assert_eq!(deserialized.email, user.email);
assert_eq!(deserialized.id, user.id);
}
#[test]
fn test_version_constant() {
assert!(!VERSION.is_empty());
assert_eq!(VERSION, env!("CARGO_PKG_VERSION"));
}
struct MockPool;
#[cfg(test)]
mod repository_tests {
use super::*;
#[test]
fn test_postgres_repository_creation() {
let table_name = "test_users";
assert_eq!(table_name, "test_users");
fn create_repository<T: for<'a> FromRow<'a, PgRow> + Send + Unpin>(
_pool: PgPool,
_table_name: &str,
) -> PostgresRepository<T> {
panic!("This would need a real pool");
}
let _repo_creator: fn(PgPool, &str) -> PostgresRepository<TestUser> = create_repository;
}
#[test]
fn test_database_operations_trait() {
fn requires_database_operations<T, R>(_: R)
where
T: for<'a> FromRow<'a, PgRow> + Send + Unpin,
R: DatabaseOperations<T>,
{
}
let _type_check = std::marker::PhantomData::<fn(PostgresRepository<TestUser>)>;
}
}
#[cfg(test)]
mod integration_test_setup {
use super::*;
#[test]
fn test_entity_table_name_constant() {
assert_eq!(TestUser::table_name(), "test_users");
assert!(!TestUser::table_name().is_empty());
assert!(TestUser::table_name().starts_with("test_"));
}
#[test]
fn test_complete_entity_lifecycle() {
let mut user = TestUser::new("lifecycle", "lifecycle@example.com");
assert!(!user.is_deleted());
assert!(user.created_at().is_some());
assert!(user.updated_at().is_some());
assert_eq!(user.username, "lifecycle");
assert_eq!(user.email, "lifecycle@example.com");
user.deleted_at = Some(chrono::Utc::now().naive_utc());
assert!(user.is_deleted());
user.deleted_at = None;
assert!(!user.is_deleted());
let original_created = user.created_at;
let original_updated = user.updated_at;
user.updated_at = chrono::Utc::now().naive_utc();
assert_eq!(user.created_at, original_created);
assert!(user.updated_at > original_updated);
}
#[test]
fn test_complex_filter_scenarios() {
let mut filters = FilterParams::default();
filters.conditions.insert("status".to_string(), FilterCondition::In(vec!["active".to_string(), "pending".to_string()]));
filters.conditions.insert("age".to_string(), FilterCondition::GreaterThan("18".to_string()));
filters.conditions.insert("name".to_string(), FilterCondition::Like("%john%".to_string()));
filters.conditions.insert("deleted_at".to_string(), FilterCondition::IsNull);
filters.conditions.insert("email_verified".to_string(), FilterCondition::Equals("true".to_string()));
assert_eq!(filters.conditions.len(), 5);
match &filters.conditions["status"] {
FilterCondition::In(values) => {
assert_eq!(values.len(), 2);
assert!(values.contains(&"active".to_string()));
}
_ => panic!("Expected In condition for status"),
}
match &filters.conditions["age"] {
FilterCondition::GreaterThan(value) => {
assert_eq!(value, "18");
}
_ => panic!("Expected GreaterThan condition for age"),
}
match &filters.conditions["name"] {
FilterCondition::Like(pattern) => {
assert_eq!(pattern, "%john%");
}
_ => panic!("Expected Like condition for name"),
}
match &filters.conditions["deleted_at"] {
FilterCondition::IsNull => {
}
_ => panic!("Expected IsNull condition for deleted_at"),
}
}
#[test]
fn test_sorting_comprehensive() {
let sort_scenarios = vec![
SortParams {
field: "created_at".to_string(),
direction: SortDirection::Desc,
},
SortParams {
field: "username".to_string(),
direction: SortDirection::Asc,
},
SortParams {
field: "email".to_string(),
direction: SortDirection::Asc,
},
SortParams {
field: "updated_at".to_string(),
direction: SortDirection::Desc,
},
];
for sort in sort_scenarios {
assert!(!sort.field.is_empty());
match sort.direction {
SortDirection::Asc | SortDirection::Desc => {
}
}
}
}
#[test]
fn test_pagination_edge_cases() {
let test_cases = vec![
(1, 10, 0, 10), (5, 20, 80, 20), (10, 5, 45, 5), (1, 1, 0, 1), (100, 100, 9900, 100), ];
for (page, per_page, expected_offset, expected_limit) in test_cases {
let params = PaginationParams::new(page, per_page);
assert_eq!(params.offset(), expected_offset, "Offset mismatch for page {}, per_page {}", page, per_page);
assert_eq!(params.limit(), expected_limit, "Limit mismatch for page {}, per_page {}", page, per_page);
}
}
#[test]
fn test_pagination_normalization() {
let test_cases = vec![
(0, 0, 1, 1), (0, 50, 1, 50), (5, 0, 5, 1), (1, 150, 1, 100), (-5, 20, 1, 20), ];
for (page, per_page, expected_page, expected_per_page) in test_cases {
let page = if page < 0 { 1 } else { page as u32 }; let params = PaginationParams::new(page, per_page);
assert_eq!(params.page, expected_page, "Page normalization failed for ({}, {})", page, per_page);
assert_eq!(params.per_page, expected_per_page, "Per_page normalization failed for ({}, {})", page, per_page);
}
}
#[test]
fn test_result_serialization_roundtrip() {
let original_data = vec![
TestUser::new("user1", "user1@example.com"),
TestUser::new("user2", "user2@example.com"),
TestUser::new("user3", "user3@example.com"),
];
let original_pagination = PaginationInfo::new(2, 5, 25);
let original_result = PaginatedResult {
data: original_data,
pagination: original_pagination,
};
let json = serde_json::to_string_pretty(&original_result).unwrap();
let deserialized: PaginatedResult<TestUser> = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.data.len(), original_result.data.len());
assert_eq!(deserialized.pagination.page, original_result.pagination.page);
assert_eq!(deserialized.pagination.per_page, original_result.pagination.per_page);
assert_eq!(deserialized.pagination.total, original_result.pagination.total);
assert_eq!(deserialized.pagination.total_pages, original_result.pagination.total_pages);
for (original, deserialized) in original_result.data.iter().zip(deserialized.data.iter()) {
assert_eq!(original.username, deserialized.username);
assert_eq!(original.email, deserialized.email);
assert_eq!(original.id, deserialized.id);
}
}
}