adjusted tests for the put request

This commit is contained in:
filipriec
2025-07-09 22:24:33 +02:00
parent 26898d474f
commit 60cc0e562e
4 changed files with 557 additions and 4 deletions

View File

@@ -2,5 +2,5 @@
// pub mod get; // pub mod get;
// pub mod delete; // pub mod delete;
pub mod post; // pub mod post;
// pub mod put; pub mod put;

View File

@@ -1,4 +1,4 @@
// tests/tables_data/post/mod.rs // tests/tables_data/post/mod.rs
// pub mod post_table_data_test; pub mod post_table_data_test;
pub mod post_table_data_steel_decimal_test; pub mod post_table_data_steel_decimal_test;

View File

@@ -1,3 +1,4 @@
// tests/tables_data/put/mod.rs // tests/tables_data/put/mod.rs
pub mod put_table_data_test; // pub mod put_table_data_test;
pub mod put_table_data_steel_decimal_test;

View File

@@ -0,0 +1,552 @@
// tests/tables_data/put/put_table_data_steel_decimal_test.rs
use sqlx::PgPool;
use common::proto::multieko2::{
table_definition::{PostTableDefinitionRequest, ColumnDefinition},
table_script::PostTableScriptRequest,
tables_data::{PostTableDataRequest, PutTableDataRequest},
};
use prost_types::value::Kind;
use prost_types::Value as ProtoValue;
use std::collections::HashMap;
use tokio::sync::mpsc;
use server::indexer::IndexCommand;
use server::table_definition::handlers::post_table_definition::post_table_definition;
use server::table_script::handlers::post_table_script::post_table_script;
use server::tables_data::handlers::post_table_data::post_table_data;
use server::tables_data::handlers::put_table_data::put_table_data;
async fn setup_test_schema(pool: &PgPool, profile_name: &str, table_name: &str) -> i64 {
// Create table definition
let request = PostTableDefinitionRequest {
profile_name: profile_name.to_string(),
table_name: table_name.to_string(),
columns: vec![
ColumnDefinition {
name: "price".to_string(),
field_type: "decimal(10, 2)".to_string(),
},
ColumnDefinition {
name: "quantity".to_string(),
field_type: "integer".to_string(),
},
ColumnDefinition {
name: "total".to_string(),
field_type: "decimal(10, 2)".to_string(),
},
ColumnDefinition {
name: "percentage".to_string(),
field_type: "decimal(28, 24)".to_string(),
},
],
indexes: vec![],
links: vec![],
};
let response = post_table_definition(pool, request).await.unwrap();
assert!(response.success);
// Get table definition ID
let schema_row = sqlx::query!("SELECT id FROM schemas WHERE name = $1", profile_name)
.fetch_one(pool)
.await
.unwrap();
let table_row = sqlx::query!(
"SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
schema_row.id,
table_name
)
.fetch_one(pool)
.await
.unwrap();
table_row.id
}
async fn create_indexer_channel() -> mpsc::Sender<IndexCommand> {
let (tx, mut rx) = mpsc::channel(100);
tokio::spawn(async move {
while let Some(_) = rx.recv().await {
// Just consume messages for testing
}
});
tx
}
// Helper function to create initial record and return its ID
async fn create_initial_record(
pool: &PgPool,
profile_name: &str,
table_name: &str,
indexer_tx: &mpsc::Sender<IndexCommand>,
) -> i64 {
let mut data = HashMap::new();
data.insert("price".to_string(), ProtoValue {
kind: Some(Kind::StringValue("10.00".to_string())),
});
data.insert("quantity".to_string(), ProtoValue {
kind: Some(Kind::NumberValue(1.0)),
});
data.insert("total".to_string(), ProtoValue {
kind: Some(Kind::StringValue("10.00".to_string())),
});
// Add percentage to satisfy all potential validation scripts during creation
data.insert("percentage".to_string(), ProtoValue {
kind: Some(Kind::StringValue("100.00".to_string())),
});
let data_request = PostTableDataRequest {
profile_name: profile_name.to_string(),
table_name: table_name.to_string(),
data,
};
let response = post_table_data(pool, data_request, indexer_tx).await.unwrap();
response.inserted_id
}
#[sqlx::test]
async fn test_put_basic_arithmetic_validation_success(pool: PgPool) {
let table_def_id = setup_test_schema(&pool, "test_put_arithmetic", "invoice").await;
let indexer_tx = create_indexer_channel().await;
let script_request = PostTableScriptRequest {
table_definition_id: table_def_id,
target_column: "total".to_string(),
script: "(* $price $quantity)".to_string(),
description: "Total = Price × Quantity".to_string(),
};
post_table_script(&pool, script_request).await.unwrap();
let record_id = create_initial_record(&pool, "test_put_arithmetic", "invoice", &indexer_tx).await;
let mut update_data = HashMap::new();
update_data.insert("price".to_string(), ProtoValue {
kind: Some(Kind::StringValue("25.50".to_string())),
});
update_data.insert("quantity".to_string(), ProtoValue {
kind: Some(Kind::NumberValue(3.0)),
});
update_data.insert("total".to_string(), ProtoValue {
kind: Some(Kind::StringValue("76.50".to_string())),
});
let put_request = PutTableDataRequest {
profile_name: "test_put_arithmetic".to_string(),
table_name: "invoice".to_string(),
id: record_id,
data: update_data,
};
let response = put_table_data(&pool, put_request, &indexer_tx).await.unwrap();
assert!(response.success);
assert_eq!(response.updated_id, record_id);
}
#[sqlx::test]
async fn test_put_basic_arithmetic_validation_failure(pool: PgPool) {
let table_def_id = setup_test_schema(&pool, "test_put_arithmetic_fail", "invoice").await;
let indexer_tx = create_indexer_channel().await;
let script_request = PostTableScriptRequest {
table_definition_id: table_def_id,
target_column: "total".to_string(),
script: "(* $price $quantity)".to_string(),
description: "Total = Price × Quantity".to_string(),
};
post_table_script(&pool, script_request).await.unwrap();
let record_id = create_initial_record(&pool, "test_put_arithmetic_fail", "invoice", &indexer_tx).await;
let mut update_data = HashMap::new();
update_data.insert("price".to_string(), ProtoValue {
kind: Some(Kind::StringValue("25.50".to_string())),
});
update_data.insert("quantity".to_string(), ProtoValue {
kind: Some(Kind::NumberValue(3.0)),
});
update_data.insert("total".to_string(), ProtoValue {
kind: Some(Kind::StringValue("70.00".to_string())),
});
let put_request = PutTableDataRequest {
profile_name: "test_put_arithmetic_fail".to_string(),
table_name: "invoice".to_string(),
id: record_id,
data: update_data,
};
let result = put_table_data(&pool, put_request, &indexer_tx).await;
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(error.code(), tonic::Code::InvalidArgument);
let msg = error.message();
assert!(msg.contains("Validation failed for column 'total'"));
assert!(msg.contains("Script calculated '76.5'"));
assert!(msg.contains("but user provided '70.00'"));
}
#[sqlx::test]
async fn test_put_complex_formula_validation(pool: PgPool) {
let table_def_id = setup_test_schema(&pool, "test_put_complex", "order").await;
let indexer_tx = create_indexer_channel().await;
let script_request = PostTableScriptRequest {
table_definition_id: table_def_id,
target_column: "total".to_string(),
script: "(+ (* $price $quantity) (* (* $price $quantity) 0.08))".to_string(),
description: "Total with 8% tax".to_string(),
};
post_table_script(&pool, script_request).await.unwrap();
let record_id = create_initial_record(&pool, "test_put_complex", "order", &indexer_tx).await;
let mut update_data = HashMap::new();
update_data.insert("price".to_string(), ProtoValue {
kind: Some(Kind::StringValue("100.00".to_string())),
});
update_data.insert("quantity".to_string(), ProtoValue {
kind: Some(Kind::NumberValue(2.0)),
});
update_data.insert("total".to_string(), ProtoValue {
kind: Some(Kind::StringValue("216.00".to_string())),
});
let put_request = PutTableDataRequest {
profile_name: "test_put_complex".to_string(),
table_name: "order".to_string(),
id: record_id,
data: update_data,
};
let response = put_table_data(&pool, put_request, &indexer_tx).await.unwrap();
assert!(response.success);
}
#[sqlx::test]
async fn test_put_division_with_precision(pool: PgPool) {
let table_def_id = setup_test_schema(&pool, "test_put_division", "calculation").await;
let indexer_tx = create_indexer_channel().await;
let script_request = PostTableScriptRequest {
table_definition_id: table_def_id,
target_column: "percentage".to_string(),
script: "(/ $total $price)".to_string(),
description: "Percentage = Total / Price".to_string(),
};
post_table_script(&pool, script_request).await.unwrap();
let record_id = create_initial_record(&pool, "test_put_division", "calculation", &indexer_tx).await;
let mut update_data = HashMap::new();
update_data.insert("price".to_string(), ProtoValue {
kind: Some(Kind::StringValue("3.00".to_string())),
});
update_data.insert("total".to_string(), ProtoValue {
kind: Some(Kind::StringValue("10.00".to_string())),
});
update_data.insert("percentage".to_string(), ProtoValue {
kind: Some(Kind::StringValue("3.333333333333333333333333".to_string())),
});
let put_request = PutTableDataRequest {
profile_name: "test_put_division".to_string(),
table_name: "calculation".to_string(),
id: record_id,
data: update_data,
};
let response = put_table_data(&pool, put_request, &indexer_tx).await.unwrap();
assert!(response.success);
}
#[sqlx::test]
async fn test_put_advanced_math_functions(pool: PgPool) {
let request = PostTableDefinitionRequest {
profile_name: "test_put_advanced_math".to_string(),
table_name: "calculations".to_string(),
columns: vec![
ColumnDefinition {
name: "input".to_string(),
field_type: "decimal(10, 4)".to_string(),
},
ColumnDefinition {
name: "square_root".to_string(),
field_type: "decimal(28, 24)".to_string(),
},
ColumnDefinition {
name: "power_result".to_string(),
field_type: "decimal(10, 4)".to_string(),
},
],
indexes: vec![],
links: vec![],
};
post_table_definition(&pool, request).await.unwrap();
let schema_row = sqlx::query!("SELECT id FROM schemas WHERE name = $1", "test_put_advanced_math")
.fetch_one(&pool).await.unwrap();
let table_row = sqlx::query!(
"SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
schema_row.id, "calculations"
).fetch_one(&pool).await.unwrap();
let indexer_tx = create_indexer_channel().await;
let sqrt_script = PostTableScriptRequest {
table_definition_id: table_row.id,
target_column: "square_root".to_string(),
script: "(sqrt $input)".to_string(),
description: "Square root validation".to_string(),
};
post_table_script(&pool, sqrt_script).await.unwrap();
let power_script = PostTableScriptRequest {
table_definition_id: table_row.id,
target_column: "power_result".to_string(),
script: "(^ $input 2.0)".to_string(),
description: "Power function validation".to_string(),
};
post_table_script(&pool, power_script).await.unwrap();
let mut initial_data = HashMap::new();
initial_data.insert("input".to_string(), ProtoValue { kind: Some(Kind::StringValue("4.0000".to_string())) });
initial_data.insert("square_root".to_string(), ProtoValue { kind: Some(Kind::StringValue("2.0".to_string())) });
initial_data.insert("power_result".to_string(), ProtoValue { kind: Some(Kind::StringValue("16.0000".to_string())) });
let initial_request = PostTableDataRequest {
profile_name: "test_put_advanced_math".to_string(),
table_name: "calculations".to_string(),
data: initial_data,
};
let record_id = post_table_data(&pool, initial_request, &indexer_tx).await.unwrap().inserted_id;
let mut update_data = HashMap::new();
update_data.insert("input".to_string(), ProtoValue { kind: Some(Kind::StringValue("16.0000".to_string())) });
update_data.insert("square_root".to_string(), ProtoValue { kind: Some(Kind::StringValue("4.0".to_string())) });
update_data.insert("power_result".to_string(), ProtoValue { kind: Some(Kind::StringValue("256.0000".to_string())) });
let put_request = PutTableDataRequest {
profile_name: "test_put_advanced_math".to_string(),
table_name: "calculations".to_string(),
id: record_id,
data: update_data,
};
let response = put_table_data(&pool, put_request, &indexer_tx).await.unwrap();
assert!(response.success);
}
#[sqlx::test]
async fn test_put_financial_calculations(pool: PgPool) {
let request = PostTableDefinitionRequest {
profile_name: "test_put_financial".to_string(),
table_name: "finance".to_string(),
columns: vec![
ColumnDefinition { name: "principal".to_string(), field_type: "decimal(12, 2)".to_string() },
ColumnDefinition { name: "rate".to_string(), field_type: "decimal(6, 4)".to_string() },
ColumnDefinition { name: "time".to_string(), field_type: "decimal(4, 1)".to_string() },
ColumnDefinition { name: "compound_result".to_string(), field_type: "decimal(12, 4)".to_string() },
ColumnDefinition { name: "percentage_result".to_string(), field_type: "decimal(10, 2)".to_string() },
],
indexes: vec![], links: vec![],
};
post_table_definition(&pool, request).await.unwrap();
let schema_row = sqlx::query!("SELECT id FROM schemas WHERE name = $1", "test_put_financial").fetch_one(&pool).await.unwrap();
let table_row = sqlx::query!("SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2", schema_row.id, "finance").fetch_one(&pool).await.unwrap();
let indexer_tx = create_indexer_channel().await;
let compound_script = PostTableScriptRequest {
table_definition_id: table_row.id,
target_column: "compound_result".to_string(),
script: "(* $principal (^ (+ 1.0 $rate) $time))".to_string(),
description: "Compound interest calculation".to_string(),
};
post_table_script(&pool, compound_script).await.unwrap();
let percentage_script = PostTableScriptRequest {
table_definition_id: table_row.id,
target_column: "percentage_result".to_string(),
script: "(* $principal $rate)".to_string(),
description: "Percentage calculation".to_string(),
};
post_table_script(&pool, percentage_script).await.unwrap();
let mut initial_data = HashMap::new();
initial_data.insert("principal".to_string(), ProtoValue { kind: Some(Kind::StringValue("500.00".to_string())) });
initial_data.insert("rate".to_string(), ProtoValue { kind: Some(Kind::StringValue("0.0300".to_string())) });
initial_data.insert("time".to_string(), ProtoValue { kind: Some(Kind::StringValue("1.0".to_string())) });
initial_data.insert("compound_result".to_string(), ProtoValue { kind: Some(Kind::StringValue("515.0000".to_string())) });
initial_data.insert("percentage_result".to_string(), ProtoValue { kind: Some(Kind::StringValue("15.00".to_string())) });
let initial_request = PostTableDataRequest {
profile_name: "test_put_financial".to_string(),
table_name: "finance".to_string(),
data: initial_data,
};
let record_id = post_table_data(&pool, initial_request, &indexer_tx).await.unwrap().inserted_id;
let mut update_data = HashMap::new();
update_data.insert("principal".to_string(), ProtoValue { kind: Some(Kind::StringValue("1000.00".to_string())) });
update_data.insert("rate".to_string(), ProtoValue { kind: Some(Kind::StringValue("0.0500".to_string())) });
update_data.insert("time".to_string(), ProtoValue { kind: Some(Kind::StringValue("2.0".to_string())) });
update_data.insert("compound_result".to_string(), ProtoValue { kind: Some(Kind::StringValue("1102.5000".to_string())) });
update_data.insert("percentage_result".to_string(), ProtoValue { kind: Some(Kind::StringValue("50.00".to_string())) });
let put_request = PutTableDataRequest {
profile_name: "test_put_financial".to_string(),
table_name: "finance".to_string(),
id: record_id,
data: update_data,
};
let response = put_table_data(&pool, put_request, &indexer_tx).await.unwrap();
assert!(response.success);
}
#[sqlx::test]
async fn test_put_partial_update_with_validation(pool: PgPool) {
let table_def_id = setup_test_schema(&pool, "test_put_partial", "invoice").await;
let indexer_tx = create_indexer_channel().await;
let script_request = PostTableScriptRequest {
table_definition_id: table_def_id,
target_column: "total".to_string(),
script: "(* $price $quantity)".to_string(),
description: "Total = Price × Quantity".to_string(),
};
post_table_script(&pool, script_request).await.unwrap();
let record_id = create_initial_record(&pool, "test_put_partial", "invoice", &indexer_tx).await;
// Partial update: only update quantity. Validation for total should still run and pass.
// The merged context will be { price: 10.00, quantity: 5, total: 10.00, ... }
// The script will calculate total as 10.00 * 5 = 50.00.
// Since we are not providing 'total' in the update, validation for it is skipped.
let mut update_data = HashMap::new();
update_data.insert("quantity".to_string(), ProtoValue {
kind: Some(Kind::NumberValue(5.0)),
});
let put_request = PutTableDataRequest {
profile_name: "test_put_partial".to_string(),
table_name: "invoice".to_string(),
id: record_id,
data: update_data,
};
let response = put_table_data(&pool, put_request, &indexer_tx).await.unwrap();
assert!(response.success);
// Now, test a partial update that SHOULD fail validation.
// We update quantity and provide an incorrect total.
let mut failing_update_data = HashMap::new();
failing_update_data.insert("quantity".to_string(), ProtoValue {
kind: Some(Kind::NumberValue(3.0)),
});
failing_update_data.insert("total".to_string(), ProtoValue {
kind: Some(Kind::StringValue("99.99".to_string())), // Wrong! Should be 10.00 * 3 = 30.00
});
let failing_put_request = PutTableDataRequest {
profile_name: "test_put_partial".to_string(),
table_name: "invoice".to_string(),
id: record_id,
data: failing_update_data,
};
let result = put_table_data(&pool, failing_put_request, &indexer_tx).await;
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(error.code(), tonic::Code::InvalidArgument);
assert!(error.message().contains("Script calculated '30'"));
}
#[sqlx::test]
async fn test_put_no_data_update(pool: PgPool) {
let _table_def_id = setup_test_schema(&pool, "test_put_empty", "invoice").await;
let indexer_tx = create_indexer_channel().await;
let record_id = create_initial_record(&pool, "test_put_empty", "invoice", &indexer_tx).await;
let update_data = HashMap::new();
let put_request = PutTableDataRequest {
profile_name: "test_put_empty".to_string(),
table_name: "invoice".to_string(),
id: record_id,
data: update_data,
};
let response = put_table_data(&pool, put_request, &indexer_tx).await.unwrap();
assert!(response.success);
assert!(response.message.contains("No fields to update"));
}
#[sqlx::test]
async fn test_put_record_not_found(pool: PgPool) {
let _table_def_id = setup_test_schema(&pool, "test_put_not_found", "invoice").await;
let indexer_tx = create_indexer_channel().await;
let mut update_data = HashMap::new();
update_data.insert("price".to_string(), ProtoValue {
kind: Some(Kind::StringValue("100.00".to_string())),
});
let put_request = PutTableDataRequest {
profile_name: "test_put_not_found".to_string(),
table_name: "invoice".to_string(),
id: 99999,
data: update_data,
};
let result = put_table_data(&pool, put_request, &indexer_tx).await;
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(error.code(), tonic::Code::NotFound);
assert!(error.message().contains("Record not found"));
}
#[sqlx::test]
async fn test_put_steel_script_error_handling(pool: PgPool) {
let table_def_id = setup_test_schema(&pool, "test_put_errors", "error_test").await;
let indexer_tx = create_indexer_channel().await;
let script_request = PostTableScriptRequest {
table_definition_id: table_def_id,
target_column: "total".to_string(),
script: "(/ $price 0.0)".to_string(),
description: "Error test".to_string(),
};
post_table_script(&pool, script_request).await.unwrap();
let record_id = create_initial_record(&pool, "test_put_errors", "error_test", &indexer_tx).await;
let mut update_data = HashMap::new();
update_data.insert("price".to_string(), ProtoValue {
kind: Some(Kind::StringValue("100.00".to_string())),
});
update_data.insert("total".to_string(), ProtoValue {
kind: Some(Kind::StringValue("999.99".to_string())),
});
let put_request = PutTableDataRequest {
profile_name: "test_put_errors".to_string(),
table_name: "error_test".to_string(),
id: record_id,
data: update_data,
};
let result = put_table_data(&pool, put_request, &indexer_tx).await;
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(error.code(), tonic::Code::InvalidArgument);
assert!(error.message().contains("Script execution failed"));
assert!(error.message().contains("Division by zero"));
}