Repository: robustmq/robustmq (language: rust)

Issue:
MINOR:  Add validation to the set_idempotent_data method in GrpcPlacementService and fill in the test case
### What type of enhancement is this?

Refactor

### What does the enhancement do?

Add validation to the set_idempotent_data method in GrpcPlacementService and fill in the test case. 

Tips:

You can implement the `ValidateExt` trait on `SetIdempotentDataRequest`, then invoke ` validate_ext ` function to achieve the validation functionality.

- you can locate the ValidateExt trait in the file: src\placement-center\src\server\grpc\validate.rs
- you need to add validation to the set_idempotent_data method in this file:  src\placement-center\src\server\grpc\service_placement.rs
- you need to fill in the test case in this file: src\grpc-clients\tests\placement_test.rs

references:
- https://github.com/robustmq/robustmq/pull/548)https://github.com/robustmq/robustmq/pull/548

### Implementation challenges

_No response_


Discussion / hints:
please assign me.thanks.

Fix patch:
diff --git a/src/placement-center/src/server/grpc/service_placement.rs b/src/placement-center/src/server/grpc/service_placement.rs
index 7f2d99092..ddaca4563 100644
--- a/src/placement-center/src/server/grpc/service_placement.rs
+++ b/src/placement-center/src/server/grpc/service_placement.rs
@@ -262,6 +262,7 @@ impl PlacementCenterService for GrpcPlacementService {
         request: Request<SetIdempotentDataRequest>,
     ) -> Result<Response<SetIdempotentDataReply>, Status> {
         let req = request.into_inner();
+        let _ = req.validate_ext()?;
         let data = StorageData::new(
             StorageDataType::ClusterSetIdempotentData,
             SetIdempotentDataRequest::encode_to_vec(&req),
diff --git a/src/placement-center/src/server/grpc/validate.rs b/src/placement-center/src/server/grpc/validate.rs
index d2c69668a..61f5a95f4 100644
--- a/src/placement-center/src/server/grpc/validate.rs
+++ b/src/placement-center/src/server/grpc/validate.rs
@@ -14,8 +14,8 @@
 
 use common_base::error::common::CommonError;
 use protocol::placement_center::placement_center_inner::{
-    DeleteIdempotentDataRequest, RegisterNodeRequest, SetResourceConfigRequest,
-    UnRegisterNodeRequest,
+    DeleteIdempotentDataRequest, RegisterNodeRequest, SetIdempotentDataRequest,
+    SetResourceConfigRequest, UnRegisterNodeRequest,
 };
 use tonic::Status;
 
@@ -72,3 +72,21 @@ impl ValidateExt for SetResourceConfigRequest {
         Ok(())
     }
 }
+
+impl ValidateExt for SetIdempotentDataRequest {
+    fn validate_ext(&self) -> Result<(), Status> {
+        if self.cluster_name.is_empty() {
+            return Err(Status::cancelled(
+                CommonError::ParameterCannotBeNull("cluster name".to_string()).to_string(),
+            ));
+        }
+
+        if self.producer_id.is_empty() {
+            return Err(Status::cancelled(
+                CommonError::ParameterCannotBeNull("producer id".to_string()).to_string(),
+            ));
+        }
+
+        Ok(())
+    }
+}


Test patch:
diff --git a/src/placement-center/tests/common.rs b/src/placement-center/tests/common.rs
index e7fd315cd..bd74bd1d7 100644
--- a/src/placement-center/tests/common.rs
+++ b/src/placement-center/tests/common.rs
@@ -59,3 +59,13 @@ pub fn node_ip() -> String {
 pub fn extend_info() -> String {
     "extend info".to_string()
 }
+
+#[allow(dead_code)]
+pub fn producer_id() -> String {
+    "producer id".to_string()
+}
+
+#[allow(dead_code)]
+pub fn seq_num() -> u64 {
+    4
+}
diff --git a/src/placement-center/tests/grpc_clients_test.rs b/src/placement-center/tests/grpc_clients_test.rs
index c4f42d4db..b0239387b 100644
--- a/src/placement-center/tests/grpc_clients_test.rs
+++ b/src/placement-center/tests/grpc_clients_test.rs
@@ -15,9 +15,10 @@
 mod common;
 #[cfg(test)]
 mod tests {
+    use common_base::error::common::CommonError;
     use protocol::placement_center::placement_center_inner::placement_center_service_client::PlacementCenterServiceClient;
     use protocol::placement_center::placement_center_inner::{
-        HeartbeatRequest, RegisterNodeRequest, UnRegisterNodeRequest,
+        HeartbeatRequest, RegisterNodeRequest, SetIdempotentDataRequest, UnRegisterNodeRequest,
     };
     use protocol::placement_center::placement_center_journal::engine_service_client::EngineServiceClient;
     use protocol::placement_center::placement_center_journal::{
@@ -25,8 +26,8 @@ mod tests {
     };
 
     use crate::common::{
-        cluster_name, cluster_type, extend_info, namespace, node_id, node_ip, pc_addr, shard_name,
-        shard_replica,
+        cluster_name, cluster_type, extend_info, namespace, node_id, node_ip, pc_addr, producer_id,
+        seq_num, shard_name, shard_replica,
     };
 
     #[tokio::test]
@@ -83,6 +84,53 @@ mod tests {
             .unwrap();
     }
 
+    #[tokio::test]
+    async fn test_set_idempotent_data() {
+        let mut client = PlacementCenterServiceClient::connect(pc_addr())
+            .await
+            .unwrap();
+
+        let valid_request = SetIdempotentDataRequest {
+            cluster_name: cluster_name(),
+            producer_id: producer_id(),
+            seq_num: seq_num(),
+        };
+
+        let response = client
+            .set_idempotent_data(tonic::Request::new(valid_request.clone()))
+            .await;
+
+        assert!(response.is_ok());
+
+        let invalid_fields = vec![
+            ("cluster_name", String::new()),
+            ("producer_id", String::new()),
+        ];
+
+        for (field, value) in invalid_fields {
+            let mut invalid_request = valid_request.clone();
+            match field {
+                "cluster_name" => invalid_request.cluster_name = value,
+                "producer_id" => invalid_request.producer_id = value,
+                _ => unreachable!(),
+            }
+
+            let response = client
+                .set_idempotent_data(tonic::Request::new(invalid_request))
+                .await;
+
+            if let Err(ref e) = response {
+                assert!(response.is_err());
+                assert_eq!(e.code(), tonic::Code::Cancelled);
+                assert_eq!(
+                    e.message(),
+                    CommonError::ParameterCannotBeNull(field.replace("_", " ").to_string())
+                        .to_string()
+                );
+            }
+        }
+    }
+
     #[tokio::test]
     async fn test_create_shard() {
         let mut client = EngineServiceClient::connect(pc_addr()).await.unwrap();