From 5358ddc728e853595d3efd6f0fabad843804c0d8 Mon Sep 17 00:00:00 2001 From: Krzysztof Piotrowski Date: Tue, 6 Aug 2024 13:06:41 +0000 Subject: [PATCH] feat: register the device profile operation Signed-off-by: Krzysztof Piotrowski --- .../c8y_mapper_ext/src/converter.rs | 2 + .../c8y_mapper_ext/src/operations/convert.rs | 21 +++++ .../src/operations/handlers/device_profile.rs | 84 +++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/crates/extensions/c8y_mapper_ext/src/converter.rs b/crates/extensions/c8y_mapper_ext/src/converter.rs index b85704f432b..f4c55c40567 100644 --- a/crates/extensions/c8y_mapper_ext/src/converter.rs +++ b/crates/extensions/c8y_mapper_ext/src/converter.rs @@ -1159,6 +1159,7 @@ impl CumulocityConverter { OperationType::FirmwareUpdate => { self.register_firmware_update_operation(&source) } + OperationType::DeviceProfile => self.register_device_profile_operation(&source), OperationType::Custom(c8y_op_name) => { self.register_custom_operation(&source, c8y_op_name) } @@ -2770,6 +2771,7 @@ pub(crate) mod tests { #[test_case("log_upload")] #[test_case("config_snapshot")] #[test_case("config_update")] + #[test_case("device_profile")] #[test_case("custom_op")] #[tokio::test] async fn operations_not_supported_for_services(op_type: &str) { diff --git a/crates/extensions/c8y_mapper_ext/src/operations/convert.rs b/crates/extensions/c8y_mapper_ext/src/operations/convert.rs index db155e8ec36..18bfbaed606 100644 --- a/crates/extensions/c8y_mapper_ext/src/operations/convert.rs +++ b/crates/extensions/c8y_mapper_ext/src/operations/convert.rs @@ -353,4 +353,25 @@ impl CumulocityConverter { MqttMessage::new(&topic, request.to_json()).with_retain() ]) } + + /// Converts a device_profile metadata message to supported operation "c8y_DeviceProfile" + pub fn register_device_profile_operation( + &mut self, + topic_id: &EntityTopicId, + ) -> Result, ConversionError> { + if !self.config.capabilities.device_profile { + warn!("Received device_profile metadata, however, device_profile feature is disabled"); + return Ok(vec![]); + } + + match self.register_operation(topic_id, "c8y_DeviceProfile") { + Err(err) => { + error!( + "Failed to register `device_profile` operation for {topic_id} due to: {err}" + ); + Ok(vec![]) + } + Ok(messages) => Ok(messages), + } + } } diff --git a/crates/extensions/c8y_mapper_ext/src/operations/handlers/device_profile.rs b/crates/extensions/c8y_mapper_ext/src/operations/handlers/device_profile.rs index 85f5d07790d..f0535c53e08 100644 --- a/crates/extensions/c8y_mapper_ext/src/operations/handlers/device_profile.rs +++ b/crates/extensions/c8y_mapper_ext/src/operations/handlers/device_profile.rs @@ -79,6 +79,7 @@ mod tests { use serde_json::json; use std::time::Duration; use tedge_actors::test_helpers::MessageReceiverExt; + use tedge_actors::MessageReceiver; use tedge_actors::Sender; use tedge_mqtt_ext::test_helpers::assert_received_contains_str; use tedge_mqtt_ext::test_helpers::assert_received_includes_json; @@ -88,6 +89,89 @@ mod tests { const TEST_TIMEOUT_MS: Duration = Duration::from_millis(3000); + #[tokio::test] + async fn create_device_profile_operation_file_for_main_device() { + let ttd = TempTedgeDir::new(); + let test_handle = spawn_c8y_mapper_actor(&ttd, true).await; + let TestHandle { mqtt, .. } = test_handle; + let mut mqtt = mqtt.with_timeout(TEST_TIMEOUT_MS); + + skip_init_messages(&mut mqtt).await; + + // Simulate device_profile cmd metadata message + mqtt.send(MqttMessage::new( + &Topic::new_unchecked("te/device/main///cmd/device_profile"), + "{}", + )) + .await + .expect("Send failed"); + + assert_received_contains_str(&mut mqtt, [("c8y/s/us", "114,c8y_DeviceProfile")]).await; + + // Validate if the supported operation file is created + assert!(ttd.path().join("operations/c8y/c8y_DeviceProfile").exists()); + } + + #[tokio::test] + async fn create_device_profile_operation_file_for_child_device() { + let ttd = TempTedgeDir::new(); + let test_handle = spawn_c8y_mapper_actor(&ttd, true).await; + let TestHandle { mqtt, .. } = test_handle; + let mut mqtt = mqtt.with_timeout(TEST_TIMEOUT_MS); + + skip_init_messages(&mut mqtt).await; + + // Simulate device_profile cmd metadata message + mqtt.send(MqttMessage::new( + &Topic::new_unchecked("te/device/child1///cmd/device_profile"), + "{}", + )) + .await + .expect("Send failed"); + + // Expect auto-registration message + assert_received_includes_json( + &mut mqtt, + [( + "te/device/child1//", + json!({"@type":"child-device","@id":"test-device:device:child1"}), + )], + ) + .await; + + assert_received_contains_str( + &mut mqtt, + [ + ( + "c8y/s/us", + "101,test-device:device:child1,child1,thin-edge.io-child", + ), + ( + "c8y/s/us/test-device:device:child1", + "114,c8y_DeviceProfile", + ), + ], + ) + .await; + + // Validate if the supported operation file is created + assert!(ttd + .path() + .join("operations/c8y/test-device:device:child1/c8y_DeviceProfile") + .exists()); + + // Duplicate device_profile cmd metadata message + mqtt.send(MqttMessage::new( + &Topic::new_unchecked("te/device/child1///cmd/device_profile"), + "{}", + )) + .await + .expect("Send failed"); + + // Assert that the supported ops message is not duplicated + assert_eq!(mqtt.recv().await, None); + } + #[tokio::test] async fn mapper_converts_device_profile_operation_for_main_device() { let ttd = TempTedgeDir::new();