get by count well tested
This commit is contained in:
@@ -3,10 +3,11 @@ use rstest::{fixture, rstest};
|
|||||||
use tonic;
|
use tonic;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use common::proto::multieko2::tables_data::GetTableDataCountRequest;
|
use common::proto::multieko2::tables_data::GetTableDataCountRequest;
|
||||||
|
use common::proto::multieko2::table_definition::{PostTableDefinitionRequest, ColumnDefinition};
|
||||||
use server::tables_data::handlers::get_table_data_count;
|
use server::tables_data::handlers::get_table_data_count;
|
||||||
|
use server::table_definition::handlers::post_table_definition;
|
||||||
use crate::common::setup_test_db;
|
use crate::common::setup_test_db;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
#[fixture]
|
#[fixture]
|
||||||
async fn pool() -> PgPool {
|
async fn pool() -> PgPool {
|
||||||
@@ -21,66 +22,53 @@ async fn closed_pool(#[future] pool: PgPool) -> PgPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_test_environment(pool: &PgPool) -> (String, String, i64) {
|
async fn setup_test_environment(pool: &PgPool) -> (String, String, i64) {
|
||||||
let mut tx = pool.begin().await.unwrap();
|
|
||||||
|
|
||||||
// Create unique profile and table names
|
// Create unique profile and table names
|
||||||
let profile_name = format!("test_profile_{}", Utc::now().timestamp_nanos());
|
let profile_name = format!("test_profile_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));
|
||||||
let table_name = format!("test_table_{}", Utc::now().timestamp_nanos());
|
let table_name = format!("test_table_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));
|
||||||
|
|
||||||
// Create profile
|
// Use the table definition handler to create the table properly
|
||||||
let profile_id = sqlx::query_scalar!(
|
let request = PostTableDefinitionRequest {
|
||||||
"INSERT INTO profiles (name) VALUES ($1) RETURNING id",
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.clone(),
|
||||||
|
columns: vec![
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "firma".to_string(),
|
||||||
|
field_type: "text".to_string(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indexes: vec![],
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
post_table_definition(pool, request).await.unwrap();
|
||||||
|
|
||||||
|
// Get the schema_id for cleanup
|
||||||
|
let schema_id = sqlx::query_scalar!(
|
||||||
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
profile_name
|
profile_name
|
||||||
)
|
)
|
||||||
.fetch_one(&mut *tx)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Create table definition
|
(profile_name, table_name, schema_id)
|
||||||
sqlx::query!(
|
|
||||||
r#"INSERT INTO table_definitions (profile_id, table_name, columns, indexes)
|
|
||||||
VALUES ($1, $2, $3, $4)"#,
|
|
||||||
profile_id,
|
|
||||||
table_name,
|
|
||||||
json!({}),
|
|
||||||
json!([])
|
|
||||||
)
|
|
||||||
.execute(&mut *tx)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Create actual table
|
|
||||||
sqlx::query(&format!(
|
|
||||||
r#"CREATE TABLE "{}" (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
firma TEXT NOT NULL
|
|
||||||
)"#,
|
|
||||||
table_name
|
|
||||||
))
|
|
||||||
.execute(&mut *tx)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
tx.commit().await.unwrap();
|
|
||||||
(profile_name, table_name, profile_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cleanup_test_environment(pool: &PgPool, profile_id: i64, table_name: &str) {
|
async fn cleanup_test_environment(pool: &PgPool, schema_id: i64, profile_name: &str) {
|
||||||
let mut tx = pool.begin().await.unwrap();
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
|
||||||
// Cleanup order matters!
|
// Cleanup order matters!
|
||||||
sqlx::query(&format!(r#"DROP TABLE IF EXISTS "{}" CASCADE"#, table_name))
|
sqlx::query(&format!(r#"DROP SCHEMA IF EXISTS "{}" CASCADE"#, profile_name))
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
sqlx::query!("DELETE FROM table_definitions WHERE profile_id = $1", profile_id)
|
sqlx::query!("DELETE FROM table_definitions WHERE schema_id = $1", schema_id)
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
sqlx::query!("DELETE FROM profiles WHERE id = $1", profile_id)
|
sqlx::query!("DELETE FROM schemas WHERE id = $1", schema_id)
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -92,21 +80,21 @@ async fn cleanup_test_environment(pool: &PgPool, profile_id: i64, table_name: &s
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_returns_correct_count(#[future] pool: PgPool) {
|
async fn test_returns_correct_count(#[future] pool: PgPool) {
|
||||||
let pool = pool.await;
|
let pool = pool.await;
|
||||||
let (profile_name, table_name, profile_id) = setup_test_environment(&pool).await;
|
let (profile_name, table_name, schema_id) = setup_test_environment(&pool).await;
|
||||||
|
|
||||||
// Insert test data
|
// Insert test data
|
||||||
let mut tx = pool.begin().await.unwrap();
|
let mut tx = pool.begin().await.unwrap();
|
||||||
sqlx::query(&format!(
|
sqlx::query(&format!(
|
||||||
r#"INSERT INTO "{}" (firma) VALUES ('Test 1')"#,
|
r#"INSERT INTO "{}"."{}" (firma) VALUES ('Test 1')"#,
|
||||||
table_name
|
profile_name, table_name
|
||||||
))
|
))
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
sqlx::query(&format!(
|
sqlx::query(&format!(
|
||||||
r#"INSERT INTO "{}" (firma) VALUES ('Test 2')"#,
|
r#"INSERT INTO "{}"."{}" (firma) VALUES ('Test 2')"#,
|
||||||
table_name
|
profile_name, table_name
|
||||||
))
|
))
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
@@ -121,28 +109,28 @@ async fn test_returns_correct_count(#[future] pool: PgPool) {
|
|||||||
let response = get_table_data_count(&pool, request).await.unwrap();
|
let response = get_table_data_count(&pool, request).await.unwrap();
|
||||||
assert_eq!(response.count, 2);
|
assert_eq!(response.count, 2);
|
||||||
|
|
||||||
cleanup_test_environment(&pool, profile_id, &table_name).await;
|
cleanup_test_environment(&pool, schema_id, &profile_name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_excludes_deleted_records(#[future] pool: PgPool) {
|
async fn test_excludes_deleted_records(#[future] pool: PgPool) {
|
||||||
let pool = pool.await;
|
let pool = pool.await;
|
||||||
let (profile_name, table_name, profile_id) = setup_test_environment(&pool).await;
|
let (profile_name, table_name, schema_id) = setup_test_environment(&pool).await;
|
||||||
|
|
||||||
// Insert test data
|
// Insert test data
|
||||||
let mut tx = pool.begin().await.unwrap();
|
let mut tx = pool.begin().await.unwrap();
|
||||||
sqlx::query(&format!(
|
sqlx::query(&format!(
|
||||||
r#"INSERT INTO "{}" (firma, deleted) VALUES ('Active', false)"#,
|
r#"INSERT INTO "{}"."{}" (firma, deleted) VALUES ('Active', false)"#,
|
||||||
table_name
|
profile_name, table_name
|
||||||
))
|
))
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
sqlx::query(&format!(
|
sqlx::query(&format!(
|
||||||
r#"INSERT INTO "{}" (firma, deleted) VALUES ('Deleted', true)"#,
|
r#"INSERT INTO "{}"."{}" (firma, deleted) VALUES ('Deleted', true)"#,
|
||||||
table_name
|
profile_name, table_name
|
||||||
))
|
))
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
@@ -157,25 +145,25 @@ async fn test_excludes_deleted_records(#[future] pool: PgPool) {
|
|||||||
let response = get_table_data_count(&pool, request).await.unwrap();
|
let response = get_table_data_count(&pool, request).await.unwrap();
|
||||||
assert_eq!(response.count, 1);
|
assert_eq!(response.count, 1);
|
||||||
|
|
||||||
cleanup_test_environment(&pool, profile_id, &table_name).await;
|
cleanup_test_environment(&pool, schema_id, &profile_name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_table_not_in_profile(#[future] pool: PgPool) {
|
async fn test_table_not_in_profile(#[future] pool: PgPool) {
|
||||||
let pool = pool.await;
|
let pool = pool.await;
|
||||||
let (profile_name, _, profile_id) = setup_test_environment(&pool).await;
|
let (profile_name, _, schema_id) = setup_test_environment(&pool).await;
|
||||||
|
|
||||||
// Test with non-existent table
|
// Test with non-existent table
|
||||||
let request = GetTableDataCountRequest {
|
let request = GetTableDataCountRequest {
|
||||||
profile_name,
|
profile_name: profile_name.clone(),
|
||||||
table_name: "non_existent_table".to_string(),
|
table_name: "non_existent_table".to_string(),
|
||||||
};
|
};
|
||||||
let result = get_table_data_count(&pool, request).await;
|
let result = get_table_data_count(&pool, request).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(result.unwrap_err().code(), tonic::Code::NotFound);
|
assert_eq!(result.unwrap_err().code(), tonic::Code::NotFound);
|
||||||
|
|
||||||
cleanup_test_environment(&pool, profile_id, "dummy_table").await;
|
cleanup_test_environment(&pool, schema_id, &profile_name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@@ -211,48 +199,44 @@ async fn test_database_error(#[future] closed_pool: PgPool) {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_empty_table_count(#[future] pool: PgPool) {
|
async fn test_empty_table_count(#[future] pool: PgPool) {
|
||||||
let pool = pool.await;
|
let pool = pool.await;
|
||||||
let mut tx = pool.begin().await.unwrap();
|
|
||||||
|
let profile_name = format!("empty_test_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));
|
||||||
|
|
||||||
|
// Use table definition handler to create the table
|
||||||
|
let request = PostTableDefinitionRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: "adresar".to_string(),
|
||||||
|
columns: vec![
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "name".to_string(),
|
||||||
|
field_type: "text".to_string(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indexes: vec![],
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
// Clean up existing profiles and their table definitions
|
post_table_definition(&pool, request).await.unwrap();
|
||||||
sqlx::query!("DELETE FROM table_definitions WHERE profile_id IN (SELECT id FROM profiles WHERE name LIKE 'empty_test%')")
|
|
||||||
.execute(&mut *tx)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
sqlx::query!("DELETE FROM profiles WHERE name LIKE 'empty_test%'")
|
|
||||||
.execute(&mut *tx)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let profile_name = format!("empty_test_{}", Utc::now().timestamp_nanos()); // Unique profile name
|
|
||||||
let profile_id = sqlx::query_scalar!(
|
|
||||||
"INSERT INTO profiles (name) VALUES ($1) RETURNING id",
|
|
||||||
profile_name
|
|
||||||
)
|
|
||||||
.fetch_one(&mut *tx)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
sqlx::query!(
|
|
||||||
r#"
|
|
||||||
INSERT INTO table_definitions (profile_id, table_name, columns, indexes)
|
|
||||||
VALUES ($1, $2, $3, $4)
|
|
||||||
"#,
|
|
||||||
profile_id,
|
|
||||||
"adresar",
|
|
||||||
json!({}), // columns
|
|
||||||
json!([]) // indexes
|
|
||||||
)
|
|
||||||
.execute(&mut *tx)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
tx.commit().await.unwrap();
|
|
||||||
|
|
||||||
let request = GetTableDataCountRequest {
|
let request = GetTableDataCountRequest {
|
||||||
profile_name,
|
profile_name: profile_name.clone(),
|
||||||
table_name: "adresar".to_string(),
|
table_name: "adresar".to_string(),
|
||||||
};
|
};
|
||||||
let response = get_table_data_count(&pool, request).await.unwrap();
|
let response = get_table_data_count(&pool, request).await.unwrap();
|
||||||
|
|
||||||
assert!(response.count >= 0);
|
assert!(response.count >= 0);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let schema_id = sqlx::query_scalar!(
|
||||||
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
|
profile_name
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cleanup_test_environment(&pool, schema_id, &profile_name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
include!("get_table_data_count_test2.rs");
|
||||||
|
include!("get_table_data_count_test3.rs");
|
||||||
|
|||||||
520
server/tests/tables_data/handlers/get_table_data_count_test2.rs
Normal file
520
server/tests/tables_data/handlers/get_table_data_count_test2.rs
Normal file
@@ -0,0 +1,520 @@
|
|||||||
|
// tests/tables_data/handlers/get_table_data_count_test2.rs
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_schema_with_special_characters(#[future] pool: PgPool) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let profile_name = "test_underscore_profile";
|
||||||
|
let table_name = "test_underscore_table";
|
||||||
|
|
||||||
|
// Use table definition handler to create the table
|
||||||
|
let request = PostTableDefinitionRequest {
|
||||||
|
profile_name: profile_name.to_string(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
columns: vec![
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "name".to_string(),
|
||||||
|
field_type: "text".to_string(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indexes: vec![],
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
post_table_definition(&pool, request).await.unwrap();
|
||||||
|
|
||||||
|
// Insert test data
|
||||||
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (name) VALUES ('Test Data')"#,
|
||||||
|
profile_name, table_name
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
|
let request = GetTableDataCountRequest {
|
||||||
|
profile_name: profile_name.to_string(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = get_table_data_count(&pool, request).await.unwrap();
|
||||||
|
assert_eq!(response.count, 1);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let schema_id = sqlx::query_scalar!(
|
||||||
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
|
profile_name
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cleanup_test_environment(&pool, schema_id, profile_name).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_large_dataset_count(#[future] pool: PgPool) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let profile_name = format!("large_test_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));
|
||||||
|
let table_name = "large_dataset_table";
|
||||||
|
|
||||||
|
// Use table definition handler to create the table
|
||||||
|
let request = PostTableDefinitionRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
columns: vec![
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "value".to_string(),
|
||||||
|
field_type: "integer".to_string(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indexes: vec![],
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
post_table_definition(&pool, request).await.unwrap();
|
||||||
|
|
||||||
|
// Insert 1000 records
|
||||||
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
for i in 1..=1000 {
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (value) VALUES ({})"#,
|
||||||
|
profile_name, table_name, i
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark some as deleted
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"UPDATE "{}"."{}" SET deleted = true WHERE value <= 100"#,
|
||||||
|
profile_name, table_name
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
|
let request = GetTableDataCountRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = get_table_data_count(&pool, request).await.unwrap();
|
||||||
|
assert_eq!(response.count, 900); // 1000 - 100 deleted
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let schema_id = sqlx::query_scalar!(
|
||||||
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
|
profile_name
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cleanup_test_environment(&pool, schema_id, &profile_name).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_mixed_deleted_states(#[future] pool: PgPool) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let profile_name = format!("mixed_test_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));
|
||||||
|
let table_name = "mixed_states_table";
|
||||||
|
|
||||||
|
// Use table definition handler to create the table
|
||||||
|
let request = PostTableDefinitionRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
columns: vec![
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "status".to_string(),
|
||||||
|
field_type: "text".to_string(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indexes: vec![],
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
post_table_definition(&pool, request).await.unwrap();
|
||||||
|
|
||||||
|
// Insert various combinations
|
||||||
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (status, deleted) VALUES ('active', false)"#,
|
||||||
|
profile_name, table_name
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (status, deleted) VALUES ('active', true)"#,
|
||||||
|
profile_name, table_name
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (status, deleted) VALUES ('inactive', false)"#,
|
||||||
|
profile_name, table_name
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (status, deleted) VALUES ('inactive', true)"#,
|
||||||
|
profile_name, table_name
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
|
let request = GetTableDataCountRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = get_table_data_count(&pool, request).await.unwrap();
|
||||||
|
assert_eq!(response.count, 2); // Only non-deleted records
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let schema_id = sqlx::query_scalar!(
|
||||||
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
|
profile_name
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cleanup_test_environment(&pool, schema_id, &profile_name).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_case_sensitivity_in_names(#[future] pool: PgPool) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let profile_name = "case_test_schema";
|
||||||
|
let table_name = "case_test_table";
|
||||||
|
|
||||||
|
// Use table definition handler to create the table
|
||||||
|
let request = PostTableDefinitionRequest {
|
||||||
|
profile_name: profile_name.to_string(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
columns: vec![
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "data".to_string(),
|
||||||
|
field_type: "text".to_string(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indexes: vec![],
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
post_table_definition(&pool, request).await.unwrap();
|
||||||
|
|
||||||
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (data) VALUES ('test data')"#,
|
||||||
|
profile_name, table_name
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
|
// Test exact case
|
||||||
|
let request = GetTableDataCountRequest {
|
||||||
|
profile_name: profile_name.to_string(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
};
|
||||||
|
let response = get_table_data_count(&pool, request).await.unwrap();
|
||||||
|
assert_eq!(response.count, 1);
|
||||||
|
|
||||||
|
// Test wrong case should fail
|
||||||
|
let wrong_case_request = GetTableDataCountRequest {
|
||||||
|
profile_name: "CASE_TEST_SCHEMA".to_string(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
};
|
||||||
|
let wrong_case_result = get_table_data_count(&pool, wrong_case_request).await;
|
||||||
|
assert!(wrong_case_result.is_err());
|
||||||
|
assert_eq!(wrong_case_result.unwrap_err().code(), tonic::Code::NotFound);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let schema_id = sqlx::query_scalar!(
|
||||||
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
|
profile_name
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cleanup_test_environment(&pool, schema_id, profile_name).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_concurrent_count_requests(#[future] pool: PgPool) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let profile_name = format!("concurrent_test_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));
|
||||||
|
let table_name = "concurrent_table";
|
||||||
|
|
||||||
|
// Use table definition handler to create the table
|
||||||
|
let request = PostTableDefinitionRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
columns: vec![
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "counter".to_string(),
|
||||||
|
field_type: "integer".to_string(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indexes: vec![],
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
post_table_definition(&pool, request).await.unwrap();
|
||||||
|
|
||||||
|
// Insert initial data
|
||||||
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
for i in 1..=50 {
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (counter) VALUES ({})"#,
|
||||||
|
profile_name, table_name, i
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
|
// Run multiple concurrent count requests
|
||||||
|
let mut handles = vec![];
|
||||||
|
for _ in 0..10 {
|
||||||
|
let pool_clone = pool.clone();
|
||||||
|
let profile_name_clone = profile_name.clone();
|
||||||
|
let table_name_clone = table_name.to_string();
|
||||||
|
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
let request = GetTableDataCountRequest {
|
||||||
|
profile_name: profile_name_clone,
|
||||||
|
table_name: table_name_clone,
|
||||||
|
};
|
||||||
|
get_table_data_count(&pool_clone, request).await
|
||||||
|
});
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all requests to complete
|
||||||
|
for handle in handles {
|
||||||
|
let response = handle.await.unwrap().unwrap();
|
||||||
|
assert_eq!(response.count, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let schema_id = sqlx::query_scalar!(
|
||||||
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
|
profile_name
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cleanup_test_environment(&pool, schema_id, &profile_name).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_table_without_physical_existence(#[future] pool: PgPool) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let profile_name = format!("missing_table_test_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));
|
||||||
|
let table_name = "missing_physical_table";
|
||||||
|
|
||||||
|
// Create table definition but then manually drop the physical table
|
||||||
|
let request = PostTableDefinitionRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
columns: vec![
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "data".to_string(),
|
||||||
|
field_type: "text".to_string(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indexes: vec![],
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
post_table_definition(&pool, request).await.unwrap();
|
||||||
|
|
||||||
|
// Manually drop the physical table while keeping the definition
|
||||||
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"DROP TABLE "{}"."{}" CASCADE"#,
|
||||||
|
profile_name, table_name
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
|
let request = GetTableDataCountRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = get_table_data_count(&pool, request).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err().code(), tonic::Code::Internal);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let schema_id = sqlx::query_scalar!(
|
||||||
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
|
profile_name
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cleanup_test_environment(&pool, schema_id, &profile_name).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_numeric_column_types_count(#[future] pool: PgPool) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let profile_name = format!("numeric_test_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));
|
||||||
|
let table_name = "numeric_types_table";
|
||||||
|
|
||||||
|
// Use table definition handler to create the table with various numeric types
|
||||||
|
let request = PostTableDefinitionRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
columns: vec![
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "big_number".to_string(),
|
||||||
|
field_type: "bigint".to_string(),
|
||||||
|
},
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "small_number".to_string(),
|
||||||
|
field_type: "integer".to_string(),
|
||||||
|
},
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "decimal_number".to_string(),
|
||||||
|
field_type: "decimal(10,2)".to_string(),
|
||||||
|
},
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "timestamp_col".to_string(),
|
||||||
|
field_type: "timestamptz".to_string(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indexes: vec![],
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
post_table_definition(&pool, request).await.unwrap();
|
||||||
|
|
||||||
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (big_number, small_number, decimal_number, timestamp_col)
|
||||||
|
VALUES (9223372036854775807, 2147483647, 99999999.99, NOW())"#,
|
||||||
|
profile_name, table_name
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (big_number, small_number, decimal_number, timestamp_col, deleted)
|
||||||
|
VALUES (1, 1, 1.00, NOW(), true)"#,
|
||||||
|
profile_name, table_name
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
|
let request = GetTableDataCountRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = get_table_data_count(&pool, request).await.unwrap();
|
||||||
|
assert_eq!(response.count, 1); // Only the non-deleted record
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let schema_id = sqlx::query_scalar!(
|
||||||
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
|
profile_name
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cleanup_test_environment(&pool, schema_id, &profile_name).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_indexed_columns_count(#[future] pool: PgPool) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let profile_name = format!("index_test_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));
|
||||||
|
let table_name = "indexed_table";
|
||||||
|
|
||||||
|
// Use table definition handler to create the table with indexes
|
||||||
|
let request = PostTableDefinitionRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
columns: vec![
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "searchable_field".to_string(),
|
||||||
|
field_type: "text".to_string(),
|
||||||
|
},
|
||||||
|
ColumnDefinition {
|
||||||
|
name: "category".to_string(),
|
||||||
|
field_type: "text".to_string(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indexes: vec!["searchable_field".to_string(), "category".to_string()],
|
||||||
|
links: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
post_table_definition(&pool, request).await.unwrap();
|
||||||
|
|
||||||
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
for i in 1..=20 {
|
||||||
|
sqlx::query(&format!(
|
||||||
|
r#"INSERT INTO "{}"."{}" (searchable_field, category) VALUES ('data_{}', 'cat_{}')"#,
|
||||||
|
profile_name, table_name, i, i % 3
|
||||||
|
))
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
|
let request = GetTableDataCountRequest {
|
||||||
|
profile_name: profile_name.clone(),
|
||||||
|
table_name: table_name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = get_table_data_count(&pool, request).await.unwrap();
|
||||||
|
assert_eq!(response.count, 20);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let schema_id = sqlx::query_scalar!(
|
||||||
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
|
profile_name
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cleanup_test_environment(&pool, schema_id, &profile_name).await;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// tests/tables_data/mod.rs
|
// tests/tables_data/mod.rs
|
||||||
|
|
||||||
// pub mod get_table_data_count_test;
|
pub mod get_table_data_count_test;
|
||||||
// pub mod get_table_data_by_position_test;
|
// pub mod get_table_data_by_position_test;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// tests/tables_data/mod.rs
|
// tests/tables_data/mod.rs
|
||||||
|
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
pub mod get;
|
// pub mod get;
|
||||||
pub mod delete;
|
// pub mod delete;
|
||||||
pub mod post;
|
// pub mod post;
|
||||||
pub mod put;
|
// pub mod put;
|
||||||
|
|||||||
Reference in New Issue
Block a user