put working now doing post general test
This commit is contained in:
@@ -6,6 +6,7 @@ import "common.proto";
|
|||||||
|
|
||||||
service TablesData {
|
service TablesData {
|
||||||
rpc PostTableData (PostTableDataRequest) returns (PostTableDataResponse);
|
rpc PostTableData (PostTableDataRequest) returns (PostTableDataResponse);
|
||||||
|
rpc PutTableData (PutTableDataRequest) returns (PutTableDataResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
message PostTableDataRequest {
|
message PostTableDataRequest {
|
||||||
@@ -19,3 +20,16 @@ message PostTableDataResponse {
|
|||||||
string message = 2;
|
string message = 2;
|
||||||
int64 inserted_id = 3;
|
int64 inserted_id = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message PutTableDataRequest {
|
||||||
|
string profile_name = 1;
|
||||||
|
string table_name = 2;
|
||||||
|
int64 id = 3;
|
||||||
|
map<string, string> data = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PutTableDataResponse {
|
||||||
|
bool success = 1;
|
||||||
|
string message = 2;
|
||||||
|
int64 updated_id = 3;
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -20,6 +20,29 @@ pub struct PostTableDataResponse {
|
|||||||
#[prost(int64, tag = "3")]
|
#[prost(int64, tag = "3")]
|
||||||
pub inserted_id: i64,
|
pub inserted_id: i64,
|
||||||
}
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct PutTableDataRequest {
|
||||||
|
#[prost(string, tag = "1")]
|
||||||
|
pub profile_name: ::prost::alloc::string::String,
|
||||||
|
#[prost(string, tag = "2")]
|
||||||
|
pub table_name: ::prost::alloc::string::String,
|
||||||
|
#[prost(int64, tag = "3")]
|
||||||
|
pub id: i64,
|
||||||
|
#[prost(map = "string, string", tag = "4")]
|
||||||
|
pub data: ::std::collections::HashMap<
|
||||||
|
::prost::alloc::string::String,
|
||||||
|
::prost::alloc::string::String,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct PutTableDataResponse {
|
||||||
|
#[prost(bool, tag = "1")]
|
||||||
|
pub success: bool,
|
||||||
|
#[prost(string, tag = "2")]
|
||||||
|
pub message: ::prost::alloc::string::String,
|
||||||
|
#[prost(int64, tag = "3")]
|
||||||
|
pub updated_id: i64,
|
||||||
|
}
|
||||||
/// Generated client implementations.
|
/// Generated client implementations.
|
||||||
pub mod tables_data_client {
|
pub mod tables_data_client {
|
||||||
#![allow(
|
#![allow(
|
||||||
@@ -137,6 +160,32 @@ pub mod tables_data_client {
|
|||||||
);
|
);
|
||||||
self.inner.unary(req, path, codec).await
|
self.inner.unary(req, path, codec).await
|
||||||
}
|
}
|
||||||
|
pub async fn put_table_data(
|
||||||
|
&mut self,
|
||||||
|
request: impl tonic::IntoRequest<super::PutTableDataRequest>,
|
||||||
|
) -> std::result::Result<
|
||||||
|
tonic::Response<super::PutTableDataResponse>,
|
||||||
|
tonic::Status,
|
||||||
|
> {
|
||||||
|
self.inner
|
||||||
|
.ready()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tonic::Status::unknown(
|
||||||
|
format!("Service was not ready: {}", e.into()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let codec = tonic::codec::ProstCodec::default();
|
||||||
|
let path = http::uri::PathAndQuery::from_static(
|
||||||
|
"/multieko2.tables_data.TablesData/PutTableData",
|
||||||
|
);
|
||||||
|
let mut req = request.into_request();
|
||||||
|
req.extensions_mut()
|
||||||
|
.insert(
|
||||||
|
GrpcMethod::new("multieko2.tables_data.TablesData", "PutTableData"),
|
||||||
|
);
|
||||||
|
self.inner.unary(req, path, codec).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Generated server implementations.
|
/// Generated server implementations.
|
||||||
@@ -159,6 +208,13 @@ pub mod tables_data_server {
|
|||||||
tonic::Response<super::PostTableDataResponse>,
|
tonic::Response<super::PostTableDataResponse>,
|
||||||
tonic::Status,
|
tonic::Status,
|
||||||
>;
|
>;
|
||||||
|
async fn put_table_data(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<super::PutTableDataRequest>,
|
||||||
|
) -> std::result::Result<
|
||||||
|
tonic::Response<super::PutTableDataResponse>,
|
||||||
|
tonic::Status,
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TablesDataServer<T> {
|
pub struct TablesDataServer<T> {
|
||||||
@@ -281,6 +337,51 @@ pub mod tables_data_server {
|
|||||||
};
|
};
|
||||||
Box::pin(fut)
|
Box::pin(fut)
|
||||||
}
|
}
|
||||||
|
"/multieko2.tables_data.TablesData/PutTableData" => {
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
struct PutTableDataSvc<T: TablesData>(pub Arc<T>);
|
||||||
|
impl<
|
||||||
|
T: TablesData,
|
||||||
|
> tonic::server::UnaryService<super::PutTableDataRequest>
|
||||||
|
for PutTableDataSvc<T> {
|
||||||
|
type Response = super::PutTableDataResponse;
|
||||||
|
type Future = BoxFuture<
|
||||||
|
tonic::Response<Self::Response>,
|
||||||
|
tonic::Status,
|
||||||
|
>;
|
||||||
|
fn call(
|
||||||
|
&mut self,
|
||||||
|
request: tonic::Request<super::PutTableDataRequest>,
|
||||||
|
) -> Self::Future {
|
||||||
|
let inner = Arc::clone(&self.0);
|
||||||
|
let fut = async move {
|
||||||
|
<T as TablesData>::put_table_data(&inner, request).await
|
||||||
|
};
|
||||||
|
Box::pin(fut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let accept_compression_encodings = self.accept_compression_encodings;
|
||||||
|
let send_compression_encodings = self.send_compression_encodings;
|
||||||
|
let max_decoding_message_size = self.max_decoding_message_size;
|
||||||
|
let max_encoding_message_size = self.max_encoding_message_size;
|
||||||
|
let inner = self.inner.clone();
|
||||||
|
let fut = async move {
|
||||||
|
let method = PutTableDataSvc(inner);
|
||||||
|
let codec = tonic::codec::ProstCodec::default();
|
||||||
|
let mut grpc = tonic::server::Grpc::new(codec)
|
||||||
|
.apply_compression_config(
|
||||||
|
accept_compression_encodings,
|
||||||
|
send_compression_encodings,
|
||||||
|
)
|
||||||
|
.apply_max_message_size_config(
|
||||||
|
max_decoding_message_size,
|
||||||
|
max_encoding_message_size,
|
||||||
|
);
|
||||||
|
let res = grpc.unary(method, req).await;
|
||||||
|
Ok(res)
|
||||||
|
};
|
||||||
|
Box::pin(fut)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut response = http::Response::new(empty_body());
|
let mut response = http::Response::new(empty_body());
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
// src/server/services/tables_data_service.rs
|
// src/server/services/tables_data_service.rs
|
||||||
use tonic::{Request, Response, Status};
|
use tonic::{Request, Response, Status};
|
||||||
use common::proto::multieko2::tables_data::tables_data_server::TablesData;
|
use common::proto::multieko2::tables_data::tables_data_server::TablesData;
|
||||||
use common::proto::multieko2::tables_data::{PostTableDataRequest, PostTableDataResponse};
|
use common::proto::multieko2::tables_data::{
|
||||||
use crate::tables_data::handlers::post_table_data;
|
PostTableDataRequest, PostTableDataResponse,
|
||||||
|
PutTableDataRequest, PutTableDataResponse // Add this import
|
||||||
|
};
|
||||||
|
use crate::tables_data::handlers::{post_table_data, put_table_data}; // Add put_table_data
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -18,6 +21,16 @@ impl TablesData for TablesDataService {
|
|||||||
) -> Result<Response<PostTableDataResponse>, Status> {
|
) -> Result<Response<PostTableDataResponse>, Status> {
|
||||||
let request = request.into_inner();
|
let request = request.into_inner();
|
||||||
let response = post_table_data(&self.db_pool, request).await?;
|
let response = post_table_data(&self.db_pool, request).await?;
|
||||||
Ok(Response::new(response)) // Wrap the response in a Response
|
Ok(Response::new(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new method implementation
|
||||||
|
async fn put_table_data(
|
||||||
|
&self,
|
||||||
|
request: Request<PutTableDataRequest>,
|
||||||
|
) -> Result<Response<PutTableDataResponse>, Status> {
|
||||||
|
let request = request.into_inner();
|
||||||
|
let response = put_table_data(&self.db_pool, request).await?;
|
||||||
|
Ok(Response::new(response))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
server/src/tables_data/docs/put_data.txt
Normal file
36
server/src/tables_data/docs/put_data.txt
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
❯ grpcurl -plaintext -d '{
|
||||||
|
"profile_name": "default",
|
||||||
|
"table_name": "2025_company_data1",
|
||||||
|
"id": 1,
|
||||||
|
"data": {
|
||||||
|
"firma": "ACME Corporation Updated",
|
||||||
|
"company_name": "ACME Corp Updated",
|
||||||
|
"textfield": "Updated sample text",
|
||||||
|
"textfield2": "Updated additional information",
|
||||||
|
"textfield3": "Updated more details",
|
||||||
|
"headquarters_psc": "54321",
|
||||||
|
"contact_phone": "987-654-3210",
|
||||||
|
"office_address": "456 Updated St, Springfield",
|
||||||
|
"support_email": "updated-support@acmecorp.com",
|
||||||
|
"is_active": "false"
|
||||||
|
}
|
||||||
|
}' localhost:50051 multieko2.tables_data.TablesData/PutTableData
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Data updated successfully",
|
||||||
|
"updatedId": "1"
|
||||||
|
}
|
||||||
|
❯ grpcurl -plaintext -d '{
|
||||||
|
"profile_name": "default",
|
||||||
|
"table_name": "2025_company_data1",
|
||||||
|
"id": 2,
|
||||||
|
"data": {
|
||||||
|
"firma": "1",
|
||||||
|
"is_active": "true"
|
||||||
|
}
|
||||||
|
}' localhost:50051 multieko2.tables_data.TablesData/PutTableData
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Data updated successfully",
|
||||||
|
"updatedId": "2"
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
// server/src/tables_data/handlers.rs
|
// server/src/tables_data/handlers.rs
|
||||||
pub mod post_table_data;
|
pub mod post_table_data;
|
||||||
|
pub mod put_table_data;
|
||||||
|
|
||||||
pub use post_table_data::post_table_data;
|
pub use post_table_data::post_table_data;
|
||||||
|
pub use put_table_data::put_table_data;
|
||||||
|
|||||||
140
server/src/tables_data/handlers/put_table_data.rs
Normal file
140
server/src/tables_data/handlers/put_table_data.rs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
// src/tables_data/handlers/put_table_data.rs
|
||||||
|
use tonic::Status;
|
||||||
|
use sqlx::{PgPool, Arguments, Postgres};
|
||||||
|
use sqlx::postgres::PgArguments;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use common::proto::multieko2::tables_data::{PutTableDataRequest, PutTableDataResponse};
|
||||||
|
|
||||||
|
pub async fn put_table_data(
|
||||||
|
db_pool: &PgPool,
|
||||||
|
request: PutTableDataRequest,
|
||||||
|
) -> Result<PutTableDataResponse, Status> {
|
||||||
|
let profile_name = request.profile_name;
|
||||||
|
let table_name = request.table_name;
|
||||||
|
let record_id = request.id;
|
||||||
|
let data = request.data;
|
||||||
|
|
||||||
|
// Lookup profile (same as POST)
|
||||||
|
let profile = sqlx::query!(
|
||||||
|
"SELECT id FROM profiles WHERE name = $1",
|
||||||
|
profile_name
|
||||||
|
)
|
||||||
|
.fetch_optional(db_pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Status::internal(format!("Profile lookup error: {}", e)))?;
|
||||||
|
|
||||||
|
let profile_id = profile.ok_or_else(|| Status::not_found("Profile not found"))?.id;
|
||||||
|
|
||||||
|
// Lookup table_definition (same as POST)
|
||||||
|
let table_def = sqlx::query!(
|
||||||
|
r#"SELECT id, columns FROM table_definitions
|
||||||
|
WHERE profile_id = $1 AND table_name = $2"#,
|
||||||
|
profile_id,
|
||||||
|
table_name
|
||||||
|
)
|
||||||
|
.fetch_optional(db_pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Status::internal(format!("Table lookup error: {}", e)))?;
|
||||||
|
|
||||||
|
let table_def = table_def.ok_or_else(|| Status::not_found("Table not found"))?;
|
||||||
|
|
||||||
|
// Parse columns from JSON (same as POST)
|
||||||
|
let columns_json: Vec<String> = serde_json::from_value(table_def.columns.clone())
|
||||||
|
.map_err(|e| Status::internal(format!("Column parsing error: {}", e)))?;
|
||||||
|
|
||||||
|
let mut columns = Vec::new();
|
||||||
|
for col_def in columns_json {
|
||||||
|
let parts: Vec<&str> = col_def.splitn(2, ' ').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
return Err(Status::internal("Invalid column format"));
|
||||||
|
}
|
||||||
|
let name = parts[0].trim_matches('"').to_string();
|
||||||
|
let sql_type = parts[1].to_string();
|
||||||
|
columns.push((name, sql_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate system columns
|
||||||
|
let system_columns = ["firma", "deleted"];
|
||||||
|
let user_columns: Vec<&String> = columns.iter().map(|(name, _)| name).collect();
|
||||||
|
|
||||||
|
// Validate input columns
|
||||||
|
for key in data.keys() {
|
||||||
|
if !system_columns.contains(&key.as_str()) && !user_columns.contains(&key) {
|
||||||
|
return Err(Status::invalid_argument(format!("Invalid column: {}", key)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare SQL parameters
|
||||||
|
let mut params = PgArguments::default();
|
||||||
|
let mut set_clauses = Vec::new();
|
||||||
|
let mut param_idx = 1;
|
||||||
|
|
||||||
|
// Add data parameters
|
||||||
|
for (col, value) in &data {
|
||||||
|
let sql_type = if system_columns.contains(&col.as_str()) {
|
||||||
|
match col.as_str() {
|
||||||
|
"firma" => "TEXT",
|
||||||
|
"deleted" => "BOOLEAN",
|
||||||
|
_ => return Err(Status::invalid_argument("Invalid system column")),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
columns.iter()
|
||||||
|
.find(|(name, _)| name == col)
|
||||||
|
.map(|(_, sql_type)| sql_type.as_str())
|
||||||
|
.ok_or_else(|| Status::invalid_argument(format!("Column not found: {}", col)))?
|
||||||
|
};
|
||||||
|
|
||||||
|
match sql_type {
|
||||||
|
"TEXT" | "VARCHAR(15)" | "VARCHAR(255)" => {
|
||||||
|
if let Some(max_len) = sql_type.strip_prefix("VARCHAR(")
|
||||||
|
.and_then(|s| s.strip_suffix(')'))
|
||||||
|
.and_then(|s| s.parse::<usize>().ok())
|
||||||
|
{
|
||||||
|
if value.len() > max_len {
|
||||||
|
return Err(Status::invalid_argument(format!("Value too long for {}", col)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params.add(value);
|
||||||
|
},
|
||||||
|
"BOOLEAN" => {
|
||||||
|
let val = value.parse::<bool>()
|
||||||
|
.map_err(|_| Status::invalid_argument(format!("Invalid boolean for {}", col)))?;
|
||||||
|
params.add(val);
|
||||||
|
},
|
||||||
|
"TIMESTAMPTZ" => {
|
||||||
|
let dt = DateTime::parse_from_rfc3339(value)
|
||||||
|
.map_err(|_| Status::invalid_argument(format!("Invalid timestamp for {}", col)))?;
|
||||||
|
params.add(dt.with_timezone(&Utc));
|
||||||
|
},
|
||||||
|
_ => return Err(Status::invalid_argument(format!("Unsupported type {}", sql_type))),
|
||||||
|
}
|
||||||
|
|
||||||
|
set_clauses.push(format!("\"{}\" = ${}", col, param_idx));
|
||||||
|
param_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ID parameter at the end
|
||||||
|
params.add(record_id);
|
||||||
|
|
||||||
|
let set_clause = set_clauses.join(", ");
|
||||||
|
let sql = format!(
|
||||||
|
"UPDATE \"{}\" SET {} WHERE id = ${} AND deleted = FALSE RETURNING id",
|
||||||
|
table_name,
|
||||||
|
set_clause,
|
||||||
|
param_idx
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = sqlx::query_scalar_with::<Postgres, i64, _>(&sql, params)
|
||||||
|
.fetch_optional(db_pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Status::internal(format!("Update failed: {}", e)))?;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Some(updated_id) => Ok(PutTableDataResponse {
|
||||||
|
success: true,
|
||||||
|
message: "Data updated successfully".into(),
|
||||||
|
updated_id,
|
||||||
|
}),
|
||||||
|
None => Err(Status::not_found("Record not found or already deleted")),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
// tests/mod.rs
|
// tests/mod.rs
|
||||||
pub mod adresar;
|
pub mod adresar;
|
||||||
|
pub mod tables_data;
|
||||||
pub mod common;
|
pub mod common;
|
||||||
|
|
||||||
|
|||||||
3
server/tests/tables_data/handlers/mod.rs
Normal file
3
server/tests/tables_data/handlers/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// tests/tables_data/mod.rs
|
||||||
|
pub mod post_table_data_test;
|
||||||
|
|
||||||
241
server/tests/tables_data/handlers/post_table_data_test.rs
Normal file
241
server/tests/tables_data/handlers/post_table_data_test.rs
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
// tests/tables_data/handlers/post_table_data_test.rs
|
||||||
|
use rstest::{fixture, rstest};
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use common::proto::multieko2::tables_data::{PostTableDataRequest, PostTableDataResponse};
|
||||||
|
use server::tables_data::handlers::post_table_data;
|
||||||
|
use crate::common::setup_test_db;
|
||||||
|
use tonic;
|
||||||
|
use chrono::Utc;
|
||||||
|
|
||||||
|
// Fixtures
|
||||||
|
#[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
|
||||||
|
}
|
||||||
|
|
||||||
|
#[fixture]
|
||||||
|
fn valid_request() -> HashMap<String, String> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("firma".into(), "Test Company".into());
|
||||||
|
map.insert("kz".into(), "KZ123".into());
|
||||||
|
map.insert("drc".into(), "DRC456".into());
|
||||||
|
map.insert("ulica".into(), "Test Street".into());
|
||||||
|
map.insert("psc".into(), "12345".into());
|
||||||
|
map.insert("mesto".into(), "Test City".into());
|
||||||
|
map.insert("stat".into(), "Test Country".into());
|
||||||
|
map.insert("banka".into(), "Test Bank".into());
|
||||||
|
map.insert("ucet".into(), "123456789".into());
|
||||||
|
map.insert("skladm".into(), "Warehouse M".into());
|
||||||
|
map.insert("ico".into(), "12345678".into());
|
||||||
|
map.insert("kontakt".into(), "John Doe".into());
|
||||||
|
map.insert("telefon".into(), "+421123456789".into());
|
||||||
|
map.insert("skladu".into(), "Warehouse U".into());
|
||||||
|
map.insert("fax".into(), "+421123456700".into());
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
#[fixture]
|
||||||
|
fn minimal_request() -> HashMap<String, String> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("firma".into(), "Required Only".into());
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_table_request(data: HashMap<String, String>) -> PostTableDataRequest {
|
||||||
|
PostTableDataRequest {
|
||||||
|
profile_name: "default".into(),
|
||||||
|
table_name: "2025_adresar".into(),
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn assert_table_response(pool: &PgPool, response: &PostTableDataResponse, expected: &HashMap<String, String>) {
|
||||||
|
let row = sqlx::query!(r#"SELECT * FROM "2025_adresar" WHERE id = $1"#, response.inserted_id)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(row.firma, expected["firma"]);
|
||||||
|
assert!(!row.deleted);
|
||||||
|
|
||||||
|
// Check optional fields using direct struct access
|
||||||
|
let check_field = |field: &str, value: &str| {
|
||||||
|
let db_value = match field {
|
||||||
|
"kz" => row.kz.as_deref().unwrap_or_default(),
|
||||||
|
"drc" => row.drc.as_deref().unwrap_or_default(),
|
||||||
|
"ulica" => row.ulica.as_deref().unwrap_or_default(),
|
||||||
|
"psc" => row.psc.as_deref().unwrap_or_default(),
|
||||||
|
"mesto" => row.mesto.as_deref().unwrap_or_default(),
|
||||||
|
"stat" => row.stat.as_deref().unwrap_or_default(),
|
||||||
|
"banka" => row.banka.as_deref().unwrap_or_default(),
|
||||||
|
"ucet" => row.ucet.as_deref().unwrap_or_default(),
|
||||||
|
"skladm" => row.skladm.as_deref().unwrap_or_default(),
|
||||||
|
"ico" => row.ico.as_deref().unwrap_or_default(),
|
||||||
|
"kontakt" => row.kontakt.as_deref().unwrap_or_default(),
|
||||||
|
"telefon" => row.telefon.as_deref().unwrap_or_default(),
|
||||||
|
"skladu" => row.skladu.as_deref().unwrap_or_default(),
|
||||||
|
"fax" => row.fax.as_deref().unwrap_or_default(),
|
||||||
|
_ => panic!("Unexpected field: {}", field),
|
||||||
|
};
|
||||||
|
assert_eq!(db_value, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
check_field("kz", expected.get("kz").unwrap_or(&String::new()));
|
||||||
|
check_field("drc", expected.get("drc").unwrap_or(&String::new()));
|
||||||
|
check_field("ulica", expected.get("ulica").unwrap_or(&String::new()));
|
||||||
|
check_field("psc", expected.get("psc").unwrap_or(&String::new()));
|
||||||
|
check_field("mesto", expected.get("mesto").unwrap_or(&String::new()));
|
||||||
|
check_field("stat", expected.get("stat").unwrap_or(&String::new()));
|
||||||
|
check_field("banka", expected.get("banka").unwrap_or(&String::new()));
|
||||||
|
check_field("ucet", expected.get("ucet").unwrap_or(&String::new()));
|
||||||
|
check_field("skladm", expected.get("skladm").unwrap_or(&String::new()));
|
||||||
|
check_field("ico", expected.get("ico").unwrap_or(&String::new()));
|
||||||
|
check_field("kontakt", expected.get("kontakt").unwrap_or(&String::new()));
|
||||||
|
check_field("telefon", expected.get("telefon").unwrap_or(&String::new()));
|
||||||
|
check_field("skladu", expected.get("skladu").unwrap_or(&String::new()));
|
||||||
|
check_field("fax", expected.get("fax").unwrap_or(&String::new()));
|
||||||
|
|
||||||
|
// Handle timestamp conversion
|
||||||
|
let created_at: chrono::DateTime<Utc> = row.created_at.unwrap().into();
|
||||||
|
assert!(created_at <= Utc::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_table_data_success(
|
||||||
|
#[future] pool: PgPool,
|
||||||
|
valid_request: HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let request = create_table_request(valid_request.clone());
|
||||||
|
let response = post_table_data(&pool, request).await.unwrap();
|
||||||
|
|
||||||
|
assert!(response.inserted_id > 0);
|
||||||
|
assert!(response.success);
|
||||||
|
assert_eq!(response.message, "Data inserted successfully");
|
||||||
|
assert_table_response(&pool, &response, &valid_request).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remaining tests follow the same pattern with fixed parameter declarations
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_table_data_whitespace_trimming(
|
||||||
|
#[future] pool: PgPool,
|
||||||
|
valid_request: HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let mut request = valid_request;
|
||||||
|
request.insert("firma".into(), " Test Company ".into());
|
||||||
|
request.insert("telefon".into(), " +421123456789 ".into());
|
||||||
|
|
||||||
|
let response = post_table_data(&pool, create_table_request(request)).await.unwrap();
|
||||||
|
|
||||||
|
let row = sqlx::query!(r#"SELECT firma, telefon FROM "2025_adresar" WHERE id = $1"#, response.inserted_id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(row.firma, "Test Company");
|
||||||
|
assert_eq!(row.telefon.unwrap(), "+421123456789");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_table_data_empty_optional_fields(
|
||||||
|
#[future] pool: PgPool,
|
||||||
|
valid_request: HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let mut request = valid_request;
|
||||||
|
request.insert("telefon".into(), " ".into());
|
||||||
|
|
||||||
|
let response = post_table_data(&pool, create_table_request(request)).await.unwrap();
|
||||||
|
let telefon: Option<String> = sqlx::query_scalar!(r#"SELECT telefon FROM "2025_adresar" WHERE id = $1"#, response.inserted_id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(telefon.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed parameter declarations for remaining tests
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_table_data_invalid_firma(
|
||||||
|
#[future] pool: PgPool,
|
||||||
|
valid_request: HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let mut request = valid_request;
|
||||||
|
request.insert("firma".into(), " ".into());
|
||||||
|
|
||||||
|
let result = post_table_data(&pool, create_table_request(request)).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err().code(), tonic::Code::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_table_data_minimal_request(
|
||||||
|
#[future] pool: PgPool,
|
||||||
|
minimal_request: HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let response = post_table_data(&pool, create_table_request(minimal_request.clone())).await.unwrap();
|
||||||
|
assert!(response.inserted_id > 0);
|
||||||
|
assert_table_response(&pool, &response, &minimal_request).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_table_data_telefon_length_limit(
|
||||||
|
#[future] pool: PgPool,
|
||||||
|
valid_request: HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let mut request = valid_request;
|
||||||
|
request.insert("telefon".into(), "1".repeat(16));
|
||||||
|
|
||||||
|
let result = post_table_data(&pool, create_table_request(request)).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err().code(), tonic::Code::Internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_table_data_special_characters(
|
||||||
|
#[future] pool: PgPool,
|
||||||
|
valid_request: HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let pool = pool.await;
|
||||||
|
let mut request = valid_request;
|
||||||
|
request.insert("ulica".into(), "Náměstí 28. října 123/456".into());
|
||||||
|
|
||||||
|
let response = post_table_data(&pool, create_table_request(request)).await.unwrap();
|
||||||
|
let row = sqlx::query!(r#"SELECT ulica FROM "2025_adresar" WHERE id = $1"#, response.inserted_id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(row.ulica.unwrap(), "Náměstí 28. října 123/456");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_table_data_database_error(
|
||||||
|
#[future] closed_pool: PgPool,
|
||||||
|
minimal_request: HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let closed_pool = closed_pool.await;
|
||||||
|
let result = post_table_data(&closed_pool, create_table_request(minimal_request)).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err().code(), tonic::Code::Internal);
|
||||||
|
}
|
||||||
2
server/tests/tables_data/mod.rs
Normal file
2
server/tests/tables_data/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// tests/tables_data/mod.rs
|
||||||
|
pub mod handlers;
|
||||||
Reference in New Issue
Block a user