From 70038feb4994b48ad8aced9a73b52027816f5787 Mon Sep 17 00:00:00 2001 From: filipriec Date: Wed, 5 Mar 2025 19:36:48 +0100 Subject: [PATCH] test for get by position --- .../tables_data/handlers/post_table_data.rs | 2 + .../tables_data/handlers/put_table_data.rs | 1 + .../get_table_data_by_position_test.rs | 316 ++++++++++++++++++ server/tests/tables_data/handlers/mod.rs | 1 + 4 files changed, 320 insertions(+) create mode 100644 server/tests/tables_data/handlers/get_table_data_by_position_test.rs diff --git a/server/src/tables_data/handlers/post_table_data.rs b/server/src/tables_data/handlers/post_table_data.rs index 6bdfa0a..c4a88cc 100644 --- a/server/src/tables_data/handlers/post_table_data.rs +++ b/server/src/tables_data/handlers/post_table_data.rs @@ -120,6 +120,8 @@ pub async fn post_table_data( .ok_or_else(|| Status::invalid_argument(format!("Column not found: {}", col)))? }; + // TODO This needs heavy adjustement. More stuff to be added for user to only pick + // preprogrammed functions match sql_type { "TEXT" | "VARCHAR(15)" | "VARCHAR(255)" => { if let Some(max_len) = sql_type.strip_prefix("VARCHAR(") diff --git a/server/src/tables_data/handlers/put_table_data.rs b/server/src/tables_data/handlers/put_table_data.rs index 2a68cfb..17592b6 100644 --- a/server/src/tables_data/handlers/put_table_data.rs +++ b/server/src/tables_data/handlers/put_table_data.rs @@ -103,6 +103,7 @@ pub async fn put_table_data( .ok_or_else(|| Status::invalid_argument(format!("Column not found: {}", col)))? }; + // TODO strong testing by user pick in the future match sql_type { "TEXT" | "VARCHAR(15)" | "VARCHAR(255)" => { if let Some(max_len) = sql_type.strip_prefix("VARCHAR(") diff --git a/server/tests/tables_data/handlers/get_table_data_by_position_test.rs b/server/tests/tables_data/handlers/get_table_data_by_position_test.rs new file mode 100644 index 0000000..28ae03a --- /dev/null +++ b/server/tests/tables_data/handlers/get_table_data_by_position_test.rs @@ -0,0 +1,316 @@ +// tests/tables_data/handlers/get_table_data_by_position_test.rs +use rstest::{fixture, rstest}; +use sqlx::PgPool; +use common::proto::multieko2::tables_data::{GetTableDataByPositionRequest, GetTableDataResponse}; +use server::tables_data::handlers::get_table_data_by_position; +use crate::common::setup_test_db; +use tonic; +use std::sync::Arc; +use tokio::sync::Mutex; + +// Global mutex to prevent test interference +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 +} + +async fn setup_profile_and_table(pool: &PgPool) { + // Create default profile + sqlx::query!( + "INSERT INTO profiles (name) VALUES ('default') ON CONFLICT DO NOTHING" + ) + .execute(pool) + .await + .unwrap(); + + // Create table definition + let columns_json = serde_json::json!([ + r#""id" BIGSERIAL PRIMARY KEY"#, + r#""firma" TEXT NOT NULL"#, + r#""deleted" BOOLEAN NOT NULL DEFAULT FALSE"#, + r#""created_at" TIMESTAMPTZ DEFAULT NOW()"# + ]); + + sqlx::query!( + r#"INSERT INTO table_definitions (profile_id, table_name, columns) + SELECT id, '2025_adresar', $1 + FROM profiles WHERE name = 'default' + ON CONFLICT DO NOTHING"#, + columns_json + ) + .execute(pool) + .await + .unwrap(); +} + +async fn create_test_record(pool: &PgPool, firma: &str, deleted: bool) -> i64 { + sqlx::query_scalar!( + r#"INSERT INTO "2025_adresar" (firma, deleted) VALUES ($1, $2) RETURNING id"#, + firma, + deleted + ) + .fetch_one(pool) + .await + .unwrap() +} + +async fn cleanup_test_records(pool: &PgPool, prefix: &str) { + sqlx::query!( + r#"DELETE FROM "2025_adresar" WHERE firma LIKE $1"#, + format!("{}%", prefix) + ) + .execute(pool) + .await + .unwrap(); +} + +async fn find_position_of_record(pool: &PgPool, id: i64) -> Option { + let records = sqlx::query_scalar!( + r#"SELECT id FROM "2025_adresar" WHERE deleted = FALSE ORDER BY id ASC"# + ) + .fetch_all(pool) + .await + .unwrap(); + + records.iter() + .position(|&record_id| record_id == id) + .map(|pos| (pos + 1) as i64) +} + +#[rstest] +#[tokio::test] +async fn test_position_validation( + #[future] pool: PgPool, +) { + let pool = pool.await; + let _guard = TEST_MUTEX.lock().await; + setup_profile_and_table(&pool).await; + + // Test position 0 + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "2025_adresar".to_string(), + position: 0, + }; + let result = get_table_data_by_position(&pool, request).await; + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), tonic::Code::InvalidArgument); + + // Test negative position + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "2025_adresar".to_string(), + position: -1, + }; + let result = get_table_data_by_position(&pool, request).await; + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), tonic::Code::InvalidArgument); +} + +#[rstest] +#[tokio::test] +async fn test_profile_table_validation( + #[future] pool: PgPool, +) { + let pool = pool.await; + let _guard = TEST_MUTEX.lock().await; + setup_profile_and_table(&pool).await; + + // Test non-existent profile + let request = GetTableDataByPositionRequest { + profile_name: "ghost_profile".to_string(), + table_name: "2025_adresar".to_string(), + position: 1, + }; + let result = get_table_data_by_position(&pool, request).await; + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), tonic::Code::NotFound); + + // Test non-existent table + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "ghost_table".to_string(), + position: 1, + }; + let result = get_table_data_by_position(&pool, request).await; + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), tonic::Code::NotFound); +} + +#[rstest] +#[tokio::test] +async fn test_basic_position_retrieval( + #[future] pool: PgPool, +) { + let pool = pool.await; + let _guard = TEST_MUTEX.lock().await; + setup_profile_and_table(&pool).await; + cleanup_test_records(&pool, "PosBasicTest").await; + + // Create test records + let id1 = create_test_record(&pool, "PosBasicTest_1", false).await; + let id2 = create_test_record(&pool, "PosBasicTest_2", false).await; + let id3 = create_test_record(&pool, "PosBasicTest_3", false).await; + + // Verify 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(); + + // Test retrieval + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "2025_adresar".to_string(), + position: pos1 as i32, + }; + let response = get_table_data_by_position(&pool, request).await.unwrap(); + assert_eq!(response.data["id"], id1.to_string()); + + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "2025_adresar".to_string(), + position: pos2 as i32, + }; + let response = get_table_data_by_position(&pool, request).await.unwrap(); + assert_eq!(response.data["id"], id2.to_string()); + + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "2025_adresar".to_string(), + position: pos3 as i32, + }; + let response = get_table_data_by_position(&pool, request).await.unwrap(); + assert_eq!(response.data["id"], id3.to_string()); + + cleanup_test_records(&pool, "PosBasicTest").await; +} + +#[rstest] +#[tokio::test] +async fn test_deleted_records_excluded( + #[future] pool: PgPool, +) { + let pool = pool.await; + let _guard = TEST_MUTEX.lock().await; + setup_profile_and_table(&pool).await; + cleanup_test_records(&pool, "PosDeletedTest").await; + + // Create records + let id1 = create_test_record(&pool, "PosDeletedTest_1", false).await; + let _deleted_id = create_test_record(&pool, "PosDeletedTest_deleted", true).await; + let id2 = create_test_record(&pool, "PosDeletedTest_2", false).await; + + // Verify positions + let pos1 = find_position_of_record(&pool, id1).await.unwrap(); + let pos2 = find_position_of_record(&pool, id2).await.unwrap(); + + assert_eq!(pos2, pos1 + 1); + + // Test retrieval + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "2025_adresar".to_string(), + position: pos1 as i32, + }; + let response = get_table_data_by_position(&pool, request).await.unwrap(); + assert_eq!(response.data["id"], id1.to_string()); + + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "2025_adresar".to_string(), + position: pos2 as i32, + }; + let response = get_table_data_by_position(&pool, request).await.unwrap(); + assert_eq!(response.data["id"], id2.to_string()); + + cleanup_test_records(&pool, "PosDeletedTest").await; +} + +#[rstest] +#[tokio::test] +async fn test_position_after_deletion( + #[future] pool: PgPool, +) { + let pool = pool.await; + let _guard = TEST_MUTEX.lock().await; + setup_profile_and_table(&pool).await; + cleanup_test_records(&pool, "PosDeletionTest").await; + + // Create records + let id1 = create_test_record(&pool, "PosDeletionTest_1", false).await; + let id2 = create_test_record(&pool, "PosDeletionTest_2", false).await; + let id3 = create_test_record(&pool, "PosDeletionTest_3", false).await; + + // Delete middle record + sqlx::query!( + r#"UPDATE "2025_adresar" SET deleted = TRUE WHERE id = $1"#, + id2 + ) + .execute(&pool) + .await + .unwrap(); + + // Verify new positions + let pos1 = find_position_of_record(&pool, id1).await.unwrap(); + let pos3 = find_position_of_record(&pool, id3).await.unwrap(); + + assert_eq!(pos3, pos1 + 1); + + // Test retrieval + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "2025_adresar".to_string(), + position: pos3 as i32, + }; + let response = get_table_data_by_position(&pool, request).await.unwrap(); + assert_eq!(response.data["id"], id3.to_string()); + + cleanup_test_records(&pool, "PosDeletionTest").await; +} + +#[rstest] +#[tokio::test] +async fn test_database_error( + #[future] closed_pool: PgPool, +) { + let pool = closed_pool.await; + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "2025_adresar".to_string(), + position: 1, + }; + let result = get_table_data_by_position(&pool, request).await; + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), tonic::Code::Internal); +} + +#[rstest] +#[tokio::test] +async fn test_empty_table( + #[future] pool: PgPool, +) { + let pool = pool.await; + let _guard = TEST_MUTEX.lock().await; + setup_profile_and_table(&pool).await; + cleanup_test_records(&pool, "").await; + + let request = GetTableDataByPositionRequest { + profile_name: "default".to_string(), + table_name: "2025_adresar".to_string(), + position: 1, + }; + let result = get_table_data_by_position(&pool, request).await; + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), tonic::Code::NotFound); +} diff --git a/server/tests/tables_data/handlers/mod.rs b/server/tests/tables_data/handlers/mod.rs index f355db9..08ecdba 100644 --- a/server/tests/tables_data/handlers/mod.rs +++ b/server/tests/tables_data/handlers/mod.rs @@ -4,4 +4,5 @@ pub mod put_table_data_test; pub mod delete_table_data_test; pub mod get_table_data_test; pub mod get_table_data_count_test; +pub mod get_table_data_by_position_test;