diff --git a/server/tests/adresar/get_adresar_by_position_test.rs b/server/tests/adresar/get_adresar_by_position_test.rs new file mode 100644 index 0000000..e26d7a6 --- /dev/null +++ b/server/tests/adresar/get_adresar_by_position_test.rs @@ -0,0 +1,303 @@ +// tests/adresar/get_adresar_by_position_test.rs +use rstest::{fixture, rstest}; +use server::adresar::handlers::get_adresar_by_position; +use common::proto::multieko2::common::PositionRequest; +use crate::common::setup_test_db; +use sqlx::PgPool; +use tonic; +use std::sync::Arc; +use tokio::sync::Mutex; + +// Use a global mutex to synchronize test execution +// This prevents tests from interfering with each other +lazy_static::lazy_static! { + static ref TEST_MUTEX: Arc> = Arc::new(Mutex::new(())); +} + +#[fixture] +async fn pool() -> PgPool { + setup_test_db().await +} + +#[fixture] +async fn closed_pool(#[future] pool: PgPool) -> PgPool { + let pool = pool.await; + pool.close().await; + pool +} + +// Create a test record with specific data and delete status +async fn create_test_record(pool: &PgPool, firma: &str, deleted: bool) -> i64 { + sqlx::query_scalar!( + "INSERT INTO adresar (firma, deleted) VALUES ($1, $2) RETURNING id", + firma, + deleted + ) + .fetch_one(pool) + .await + .unwrap() +} + +// Clean up test records after tests +async fn cleanup_test_records(pool: &PgPool, prefix: &str) { + sqlx::query!( + "DELETE FROM adresar WHERE firma LIKE $1", + format!("{}%", prefix) + ) + .execute(pool) + .await + .unwrap(); +} + +// Find the position of a record in the database +async fn find_position_of_record(pool: &PgPool, id: i64) -> Option { + // Get all non-deleted records ordered by ID + let records = sqlx::query_scalar!( + "SELECT id FROM adresar WHERE deleted = FALSE ORDER BY id ASC" + ) + .fetch_all(pool) + .await + .unwrap(); + + // Find the position of our record (1-based) + for (index, record_id) in records.iter().enumerate() { + if *record_id == id { + return Some((index + 1) as i64); + } + } + + None +} + +// Test position validation +#[rstest] +#[tokio::test] +async fn test_position_zero(#[future] pool: PgPool) { + let pool = pool.await; + + // Request position 0 (invalid) + let request = PositionRequest { position: 0 }; + let result = get_adresar_by_position(&pool, request).await; + + // Verify it returns an error + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), tonic::Code::InvalidArgument); +} + +#[rstest] +#[tokio::test] +async fn test_position_negative(#[future] pool: PgPool) { + let pool = pool.await; + + // Request negative position (invalid) + let request = PositionRequest { position: -1 }; + let result = get_adresar_by_position(&pool, request).await; + + // Verify it returns an error + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), tonic::Code::InvalidArgument); +} + +#[rstest] +#[tokio::test] +async fn test_basic_position_retrieval(#[future] pool: PgPool) { + let pool = pool.await; + + // Take a lock to prevent concurrent test execution + let _guard = TEST_MUTEX.lock().await; + + // Use a unique prefix for test data to prevent conflicts + let prefix = "PosBasicTest"; + + // Clean up any existing test data + cleanup_test_records(&pool, prefix).await; + + // Create test records + let id1 = create_test_record(&pool, &format!("{}_1", prefix), false).await; + let id2 = create_test_record(&pool, &format!("{}_2", prefix), false).await; + let id3 = create_test_record(&pool, &format!("{}_3", prefix), false).await; + + // Find the positions of these records in the database + let pos1 = find_position_of_record(&pool, id1).await.unwrap(); + let pos2 = find_position_of_record(&pool, id2).await.unwrap(); + let pos3 = find_position_of_record(&pool, id3).await.unwrap(); + + // Test retrieving each position + let response1 = get_adresar_by_position(&pool, PositionRequest { position: pos1 }).await.unwrap(); + assert_eq!(response1.id, id1); + + let response2 = get_adresar_by_position(&pool, PositionRequest { position: pos2 }).await.unwrap(); + assert_eq!(response2.id, id2); + + let response3 = get_adresar_by_position(&pool, PositionRequest { position: pos3 }).await.unwrap(); + assert_eq!(response3.id, id3); + + // Clean up test data + cleanup_test_records(&pool, prefix).await; +} + +#[rstest] +#[tokio::test] +async fn test_deleted_records_excluded(#[future] pool: PgPool) { + let pool = pool.await; + + // Take a lock to prevent concurrent test execution + let _guard = TEST_MUTEX.lock().await; + + // Use a unique prefix for test data + let prefix = "PosDeletedTest"; + + // Clean up any existing test data + cleanup_test_records(&pool, prefix).await; + + // Create a mix of active and deleted records + let id1 = create_test_record(&pool, &format!("{}_1", prefix), false).await; + let _id_deleted = create_test_record(&pool, &format!("{}_del", prefix), true).await; + let id2 = create_test_record(&pool, &format!("{}_2", prefix), false).await; + + // Find positions + let pos1 = find_position_of_record(&pool, id1).await.unwrap(); + let pos2 = find_position_of_record(&pool, id2).await.unwrap(); + + // Verify positions are consecutive, which means the deleted record is excluded + assert_eq!(pos2, pos1 + 1); + + // Retrieve by position and verify + let response1 = get_adresar_by_position(&pool, PositionRequest { position: pos1 }).await.unwrap(); + assert_eq!(response1.id, id1); + + let response2 = get_adresar_by_position(&pool, PositionRequest { position: pos2 }).await.unwrap(); + assert_eq!(response2.id, id2); + + // Clean up test data + cleanup_test_records(&pool, prefix).await; +} + +#[rstest] +#[tokio::test] +async fn test_position_changes_after_deletion(#[future] pool: PgPool) { + let pool = pool.await; + + // Take a lock to prevent concurrent test execution + let _guard = TEST_MUTEX.lock().await; + + // Use a unique prefix for test data + let prefix = "PosChangeTest"; + + // Clean up any existing test data + cleanup_test_records(&pool, prefix).await; + + // Create records + let id1 = create_test_record(&pool, &format!("{}_1", prefix), false).await; + let id2 = create_test_record(&pool, &format!("{}_2", prefix), false).await; + let id3 = create_test_record(&pool, &format!("{}_3", prefix), false).await; + + // Find initial positions + let pos1 = find_position_of_record(&pool, id1).await.unwrap(); + let pos2 = find_position_of_record(&pool, id2).await.unwrap(); + let pos3 = find_position_of_record(&pool, id3).await.unwrap(); + + // Mark the first record as deleted + sqlx::query!("UPDATE adresar SET deleted = TRUE WHERE id = $1", id1) + .execute(&pool) + .await + .unwrap(); + + // Find new positions + let pos2_after = find_position_of_record(&pool, id2).await.unwrap(); + let pos3_after = find_position_of_record(&pool, id3).await.unwrap(); + + // Verify positions shifted + assert!(pos2_after < pos2); + assert!(pos3_after < pos3); + + // Verify by retrieving records at new positions + let response_at_first = get_adresar_by_position(&pool, PositionRequest { position: pos2_after }).await.unwrap(); + assert_eq!(response_at_first.id, id2); + + // Clean up test data + cleanup_test_records(&pool, prefix).await; +} + +#[rstest] +#[tokio::test] +async fn test_position_out_of_bounds(#[future] pool: PgPool) { + let pool = pool.await; + + // Take a lock to prevent concurrent test execution + let _guard = TEST_MUTEX.lock().await; + + // Get the total count of non-deleted records + let count = sqlx::query_scalar!( + "SELECT COUNT(*) FROM adresar WHERE deleted = FALSE" + ) + .fetch_one(&pool) + .await + .unwrap() + .unwrap_or(0); + + // Request a position beyond the count + let request = PositionRequest { position: count + 1 }; + let result = get_adresar_by_position(&pool, request).await; + + // Verify it returns an error + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), tonic::Code::NotFound); +} + +#[rstest] +#[tokio::test] +async fn test_database_error(#[future] closed_pool: PgPool) { + let closed_pool = closed_pool.await; + + // Attempt to query with a closed pool + let request = PositionRequest { position: 1 }; + let result = get_adresar_by_position(&closed_pool, request).await; + + // Verify it returns an internal error + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), tonic::Code::Internal); +} + +#[rstest] +#[tokio::test] +async fn test_position_after_adding_record(#[future] pool: PgPool) { + let pool = pool.await; + + // Take a lock to prevent concurrent test execution + let _guard = TEST_MUTEX.lock().await; + + // Use a unique prefix for test data + let prefix = "PosAddTest"; + + // Clean up any existing test data + cleanup_test_records(&pool, prefix).await; + + // Create records + let id1 = create_test_record(&pool, &format!("{}_1", prefix), false).await; + let id2 = create_test_record(&pool, &format!("{}_2", prefix), false).await; + + // Find positions + let pos1 = find_position_of_record(&pool, id1).await.unwrap(); + let pos2 = find_position_of_record(&pool, id2).await.unwrap(); + + // Add a new record + let id3 = create_test_record(&pool, &format!("{}_3", prefix), false).await; + + // Find its position + let pos3 = find_position_of_record(&pool, id3).await.unwrap(); + + // Verify retrieval by position + let response3 = get_adresar_by_position(&pool, PositionRequest { position: pos3 }).await.unwrap(); + assert_eq!(response3.id, id3); + + // Verify original positions still work + let response1 = get_adresar_by_position(&pool, PositionRequest { position: pos1 }).await.unwrap(); + assert_eq!(response1.id, id1); + + let response2 = get_adresar_by_position(&pool, PositionRequest { position: pos2 }).await.unwrap(); + assert_eq!(response2.id, id2); + + // Clean up test data + cleanup_test_records(&pool, prefix).await; +} diff --git a/server/tests/adresar/mod.rs b/server/tests/adresar/mod.rs index efa50cb..072cf2a 100644 --- a/server/tests/adresar/mod.rs +++ b/server/tests/adresar/mod.rs @@ -4,3 +4,4 @@ pub mod post_adresar_test; pub mod put_adresar_test; pub mod get_adresar_test; pub mod get_adresar_count_test; +pub mod get_adresar_by_position_test;