diff options
author | Lukasz Woznicki <75632179+makr11st@users.noreply.github.com> | 2021-11-24 20:54:56 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-24 20:54:56 +0000 |
commit | a4ffeccf60090e4456755bc53a6e3b8c8038e855 (patch) | |
tree | 9583f187114913a92866571920dd3bb205bd50a3 /crates/core/c8y_smartrest/src | |
parent | 8217e80670e76dbf9168780f5e0545355a39f8f3 (diff) |
Restructure directories of the workspace (#559)
* Restructure directories of the workspace
* Rename c8y_translator_lib to c8y_translator
* Update comment on how to get dummy plugin path
Signed-off-by: Lukasz Woznicki <lukasz.woznicki@softwareag.com>
Diffstat (limited to 'crates/core/c8y_smartrest/src')
-rw-r--r-- | crates/core/c8y_smartrest/src/error.rs | 35 | ||||
-rw-r--r-- | crates/core/c8y_smartrest/src/lib.rs | 3 | ||||
-rw-r--r-- | crates/core/c8y_smartrest/src/smartrest_deserializer.rs | 542 | ||||
-rw-r--r-- | crates/core/c8y_smartrest/src/smartrest_serializer.rs | 367 |
4 files changed, 947 insertions, 0 deletions
diff --git a/crates/core/c8y_smartrest/src/error.rs b/crates/core/c8y_smartrest/src/error.rs new file mode 100644 index 00000000..03fd5125 --- /dev/null +++ b/crates/core/c8y_smartrest/src/error.rs @@ -0,0 +1,35 @@ +use json_sm::SoftwareUpdateResponse; + +#[derive(thiserror::Error, Debug)] +pub enum SmartRestSerializerError { + #[error("The operation status is not supported. {response:?}")] + UnsupportedOperationStatus { response: SoftwareUpdateResponse }, + + #[error("Failed to serialize SmartREST.")] + InvalidCsv(#[from] csv::Error), + + #[error(transparent)] + FromCsvWriter(#[from] csv::IntoInnerError<csv::Writer<Vec<u8>>>), + + #[error(transparent)] + FromUtf8Error(#[from] std::string::FromUtf8Error), +} + +#[derive(thiserror::Error, Debug)] +pub enum SmartRestDeserializerError { + #[error("The received SmartREST message ID {id} is unsupported.")] + UnsupportedOperation { id: String }, + + #[error("Failed to deserialize SmartREST.")] + InvalidCsv(#[from] csv::Error), + + #[error("Jwt response contains incorrect ID: {0}")] + InvalidMessageId(u16), + + #[error("Parameter {parameter} is not recognized. {hint}")] + InvalidParameter { + operation: String, + parameter: String, + hint: String, + }, +} diff --git a/crates/core/c8y_smartrest/src/lib.rs b/crates/core/c8y_smartrest/src/lib.rs new file mode 100644 index 00000000..596af9be --- /dev/null +++ b/crates/core/c8y_smartrest/src/lib.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod smartrest_deserializer; +pub mod smartrest_serializer; diff --git a/crates/core/c8y_smartrest/src/smartrest_deserializer.rs b/crates/core/c8y_smartrest/src/smartrest_deserializer.rs new file mode 100644 index 00000000..841915b7 --- /dev/null +++ b/crates/core/c8y_smartrest/src/smartrest_deserializer.rs @@ -0,0 +1,542 @@ +use crate::error::SmartRestDeserializerError; +use chrono::{DateTime, FixedOffset}; +use csv::ReaderBuilder; +use json_sm::{DownloadInfo, SoftwareModule, SoftwareModuleUpdate, SoftwareUpdateRequest}; +use serde::de::Error; +use serde::{Deserialize, Deserializer, Serialize}; +use std::convert::{TryFrom, TryInto}; + +#[derive(Debug)] +enum CumulocitySoftwareUpdateActions { + Install, + Delete, +} + +impl TryFrom<String> for CumulocitySoftwareUpdateActions { + type Error = SmartRestDeserializerError; + + fn try_from(action: String) -> Result<Self, Self::Error> { + match action.as_str() { + "install" => Ok(Self::Install), + "delete" => Ok(Self::Delete), + param => Err(SmartRestDeserializerError::InvalidParameter { + parameter: param.into(), + operation: "c8y_SoftwareUpdate".into(), + hint: "It must be install or delete.".into(), + }), + } + } +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct SmartRestUpdateSoftware { + pub message_id: String, + pub external_id: String, + pub update_list: Vec<SmartRestUpdateSoftwareModule>, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct SmartRestUpdateSoftwareModule { + pub software: String, + pub version: Option<String>, + pub url: Option<String>, + pub action: String, +} + +impl SmartRestUpdateSoftware { + pub fn new() -> Self { + Self { + message_id: "528".into(), + external_id: "".into(), + update_list: vec![], + } + } + + pub fn from_smartrest(&self, smartrest: &str) -> Result<Self, SmartRestDeserializerError> { + let mut message_id = smartrest.to_string(); + let () = message_id.truncate(3); + + let mut rdr = ReaderBuilder::new() + .has_headers(false) + .flexible(true) + .from_reader(smartrest.as_bytes()); + let mut record: Self = Self::new(); + + for result in rdr.deserialize() { + record = result?; + } + + Ok(record) + } + + pub fn to_thin_edge_json(&self) -> Result<SoftwareUpdateRequest, SmartRestDeserializerError> { + let request = self.map_to_software_update_request(SoftwareUpdateRequest::new())?; + Ok(request) + } + + pub fn modules(&self) -> Vec<SmartRestUpdateSoftwareModule> { + let mut modules = vec![]; + for module in &self.update_list { + modules.push(SmartRestUpdateSoftwareModule { + software: module.software.clone(), + version: module.version.clone(), + url: module.url.clone(), + action: module.action.clone(), + }); + } + modules + } + + fn map_to_software_update_request( + &self, + mut request: SoftwareUpdateRequest, + ) -> Result<SoftwareUpdateRequest, SmartRestDeserializerError> { + for module in &self.modules() { + match module.action.clone().try_into()? { + CumulocitySoftwareUpdateActions::Install => { + request.add_update(SoftwareModuleUpdate::Install { + module: SoftwareModule { + module_type: module.get_module_version_and_type().1, + name: module.software.clone(), + version: module.get_module_version_and_type().0, + url: module.get_url(), + file_path: None, + }, + }); + } + CumulocitySoftwareUpdateActions::Delete => { + request.add_update(SoftwareModuleUpdate::Remove { + module: SoftwareModule { + module_type: module.get_module_version_and_type().1, + name: module.software.clone(), + version: module.get_module_version_and_type().0, + url: None, + file_path: None, + }, + }); + } + } + } + Ok(request) + } +} + +impl SmartRestUpdateSoftwareModule { + fn get_module_version_and_type(&self) -> (Option<String>, Option<String>) { + let split; + match &self.version { + Some(version) => { + if version.matches("::").count() > 1 { + split = version.rsplit_once("::"); + } else { + split = version.split_once("::"); + } + + match split { + Some((v, t)) => { + if v.is_empty() { + (None, Some(t.into())) // ::debian + } else if !t.is_empty() { + (Some(v.into()), Some(t.into())) // 1.0::debian + } else { + (Some(v.into()), None) + } + } + None => { + if version == " " { + (None, None) // as long as c8y UI forces version input + } else { + (Some(version.into()), None) // 1.0 + } + } + } + } + + None => (None, None), // (empty) + } + } + + fn get_url(&self) -> Option<DownloadInfo> { + match &self.url { + Some(url) if url.trim().is_empty() => None, + Some(url) => Some(DownloadInfo::new(url.as_str())), + None => None, + } + } +} + +fn to_datetime<'de, D>(deserializer: D) -> Result<DateTime<FixedOffset>, D::Error> +where + D: Deserializer<'de>, +{ + // NOTE `NaiveDateTime` is used here because c8y uses for log requests a date time string which + // does not exactly equal `chrono::DateTime::parse_from_rfc3339` + // c8y result: + // 2021-10-23T19:03:26+0100 + // rfc3339 expected: + // 2021-10-23T19:03:26+01:00 + // so we add a ':' + let mut date_string: String = Deserialize::deserialize(deserializer)?; + let str_size = date_string.len(); + // check if `date_string` does not have a colon. + let date_string_end = &date_string.split('+').last(); + date_string = match date_string_end { + Some(string) if !string.contains(':') => { + date_string[0..str_size - 2].to_string() + + ":" + + &date_string[str_size - 2..str_size].to_string() + } + _ => date_string, + }; + + match DateTime::parse_from_rfc3339(&date_string) { + Ok(result) => Ok(result), + Err(e) => Err(D::Error::custom(&format!("Error: {}", e))), + } +} + +pub enum SmartRestVariant { + SmartRestLogRequest, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct SmartRestLogRequest { + pub message_id: String, + pub device: String, + pub log_type: String, + #[serde(deserialize_with = "to_datetime")] + pub date_from: DateTime<FixedOffset>, + #[serde(deserialize_with = "to_datetime")] + pub date_to: DateTime<FixedOffset>, + pub needle: Option<String>, + pub lines: usize, +} + +impl SmartRestLogRequest { + pub fn from_smartrest(smartrest: &str) -> Result<Self, SmartRestDeserializerError> { + let mut rdr = ReaderBuilder::new() + .has_headers(false) + .flexible(true) + .from_reader(smartrest.as_bytes()); + + match rdr.deserialize().next() { + Some(Ok(record)) => Ok(record), + Some(Err(err)) => Err(err)?, + None => panic!("empty request"), + } + } +} + +type JwtToken = String; + +#[derive(Debug, Deserialize, PartialEq)] +pub struct SmartRestJwtResponse { + id: u16, + token: JwtToken, +} + +impl SmartRestJwtResponse { + pub fn new() -> Self { + Self { + id: 71, + token: "".into(), + } + } + + pub fn try_new(to_parse: &str) -> Result<Self, SmartRestDeserializerError> { + let mut csv = csv::ReaderBuilder::new() + .has_headers(false) + .from_reader(to_parse.as_bytes()); + + let mut jwt = Self::new(); + for result in csv.deserialize() { + jwt = result.unwrap(); + } + + if jwt.id != 71 { + return Err(SmartRestDeserializerError::InvalidMessageId(jwt.id)); + } + + Ok(jwt) + } + + pub fn token(&self) -> JwtToken { + self.token.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use assert_json_diff::*; + use json_sm::*; + use serde_json::json; + use test_case::test_case; + + // To avoid using an ID randomly generated, which is not convenient for testing. + impl SmartRestUpdateSoftware { + fn to_thin_edge_json_with_id( + &self, + id: &str, + ) -> Result<SoftwareUpdateRequest, SmartRestDeserializerError> { + let request = SoftwareUpdateRequest::new_with_id(id); + self.map_to_software_update_request(request) + } + } + + #[test] + fn jwt_token_create_new() { + let jwt = SmartRestJwtResponse::new(); + + assert!(jwt.token.is_empty()); + } + + #[test] + fn jwt_token_deserialize_correct_id_returns_token() { + let test_response = "71,123456"; + let jwt = SmartRestJwtResponse::try_new(test_response).unwrap(); + + assert_eq!(jwt.token(), "123456"); + } + + #[test] + fn jwt_token_deserialize_incorrect_id_returns_error() { + let test_response = "42,123456"; + + let jwt = SmartRestJwtResponse::try_new(test_response); + + assert!(jwt.is_err()); + assert_matches::assert_matches!(jwt, Err(SmartRestDeserializerError::InvalidMessageId(42))); + } + + #[test] + fn verify_get_module_version_and_type() { + let mut module = SmartRestUpdateSoftwareModule { + software: "software1".into(), + version: None, + url: None, + action: "install".into(), + }; // "" + assert_eq!(module.get_module_version_and_type(), (None, None)); + + module.version = Some(" ".into()); // " " (space) + assert_eq!(module.get_module_version_and_type(), (None, None)); + + module.version = Some("::debian".into()); + assert_eq!( + module.get_module_version_and_type(), + (None, Some("debian".to_string())) + ); + + module.version = Some("1.0.0::debian".into()); + assert_eq!( + module.get_module_version_and_type(), + (Some("1.0.0".to_string()), Some("debian".to_string())) + ); + + module.version = Some("1.0.0::1::debian".into()); + assert_eq!( + module.get_module_version_and_type(), + (Some("1.0.0::1".to_string()), Some("debian".to_string())) + ); + + module.version = Some("1.0.0::1::".into()); + assert_eq!( + module.get_module_version_and_type(), + (Some("1.0.0::1".to_string()), None) + ); + + module.version = Some("1.0.0".into()); + assert_eq!( + module.get_module_version_and_type(), + (Some("1.0.0".to_string()), None) + ); + } + + #[test] + fn deserialize_smartrest_update_software() { + let smartrest = + String::from("528,external_id,software1,version1,url1,install,software2,,,delete"); + let update_software = SmartRestUpdateSoftware::new() + .from_smartrest(&smartrest) + .unwrap(); + + let expected_update_software = SmartRestUpdateSoftware { + message_id: "528".into(), + external_id: "external_id".into(), + update_list: vec![ + SmartRestUpdateSoftwareModule { + software: "software1".into(), + version: Some("version1".into()), + url: Some("url1".into()), + action: "install".into(), + }, + SmartRestUpdateSoftwareModule { + software: "software2".into(), + version: None, + url: None, + action: "delete".into(), + }, + ], + }; + + assert_eq!(update_software, expected_update_software); + } + + #[test] + fn deserialize_incorrect_smartrest_message_id() { + let smartrest = String::from("516,external_id"); + assert!(SmartRestUpdateSoftware::new() + .from_smartrest(&smartrest) + .is_err()); + } + + #[test] + fn deserialize_incorrect_smartrest_action() { + let smartrest = + String::from("528,external_id,software1,version1,url1,action,software2,,,remove"); + assert!(SmartRestUpdateSoftware::new() + .from_smartrest(&smartrest) + .unwrap() + .to_thin_edge_json() + .is_err()); + } + + #[test] + fn from_smartrest_update_software_to_software_update_request() { + let smartrest_obj = SmartRestUpdateSoftware { + message_id: "528".into(), + external_id: "external_id".into(), + update_list: vec![ + SmartRestUpdateSoftwareModule { + software: "software1".into(), + version: Some("version1::debian".into()), + url: Some("url1".into()), + action: "install".into(), + }, + SmartRestUpdateSoftwareModule { + software: "software2".into(), + version: None, + url: None, + action: "delete".into(), + }, + ], + }; + let thin_edge_json = smartrest_obj.to_thin_edge_json_with_id("123").unwrap(); + + let mut expected_thin_edge_json = SoftwareUpdateRequest::new_with_id("123"); + let () = + expected_thin_edge_json.add_update(SoftwareModuleUpdate::install(SoftwareModule { + module_type: Some("debian".to_string()), + name: "software1".to_string(), + version: Some("version1".to_string()), + url: Some("url1".into()), + file_path: None, + })); + let () = expected_thin_edge_json.add_update(SoftwareModuleUpdate::remove(SoftwareModule { + module_type: Some("".to_string()), + name: "software2".to_string(), + version: None, + url: None, + file_path: None, + })); + + assert_eq!(thin_edge_json, expected_thin_edge_json); + } + + #[test] + fn from_smartrest_update_software_to_json() { + let smartrest = + String::from("528,external_id,nodered,1.0.0::debian,,install,\ + collectd,5.7::debian,https://collectd.org/download/collectd-tarballs/collectd-5.12.0.tar.bz2,install,\ + nginx,1.21.0::docker,,install,mongodb,4.4.6::docker,,delete"); + let update_software = SmartRestUpdateSoftware::new(); + let software_update_request = update_software + .from_smartrest(&smartrest) + .unwrap() + .to_thin_edge_json_with_id("123"); + let output_json = software_update_request.unwrap().to_json().unwrap(); + + let expected_json = json!({ + "id": "123", + "updateList": [ + { + "type": "debian", + "modules": [ + { + "name": "nodered", + "version": "1.0.0", + "action": "install" + }, + { + "name": "collectd", + "version": "5.7", + "url": "https://collectd.org/download/collectd-tarballs/collectd-5.12.0.tar.bz2", + "action": "install" + } + ] + }, + { + "type": "docker", + "modules": [ + { + "name": "nginx", + "version": "1.21.0", + "action": "install" + }, + { + "name": "mongodb", + "version": "4.4.6", + "action": "remove" + } + ] + } + ] + }); + assert_json_eq!( + serde_json::from_str::<serde_json::Value>(output_json.as_str()).unwrap(), + expected_json + ); + } + + #[test] + fn access_smartrest_update_modules() { + let smartrest = + String::from("528,external_id,software1,version1,url1,install,software2,,,delete"); + let update_software = SmartRestUpdateSoftware::new(); + let vec = update_software + .from_smartrest(&smartrest) + .unwrap() + .modules(); + + let expected_vec = vec![ + SmartRestUpdateSoftwareModule { + software: "software1".into(), + version: Some("version1".into()), + url: Some("url1".into()), + action: "install".into(), + }, + SmartRestUpdateSoftwareModule { + software: "software2".into(), + version: None, + url: None, + action: "delete".into(), + }, + ]; + + assert_eq!(vec, expected_vec); + } + + #[test_case("2021-09-21T11:40:27+0200", "2021-09-22T11:40:27+0200"; "c8y expected")] + #[test_case("2021-09-21T11:40:27+02:00", "2021-09-22T11:40:27+02:00"; "with colon both")] + #[test_case("2021-09-21T11:40:27+02:00", "2021-09-22T11:40:27+0200"; "with colon date from")] + #[test_case("2021-09-21T11:40:27+0200", "2021-09-22T11:40:27+02:00"; "with colon date to")] + fn deserialize_smartrest_log_file_request_operation(date_from: &str, date_to: &str) { + let smartrest = String::from(&format!( + "522,DeviceSerial,syslog,{},{},ERROR,1000", + date_from, date_to + )); + let log = SmartRestLogRequest::from_smartrest(&smartrest); + assert!(log.is_ok()); + } +} diff --git a/crates/core/c8y_smartrest/src/smartrest_serializer.rs b/crates/core/c8y_smartrest/src/smartrest_serializer.rs new file mode 100644 index 00000000..81e7ed4f --- /dev/null +++ b/crates/core/c8y_smartrest/src/smartrest_serializer.rs @@ -0,0 +1,367 @@ +use crate::error::SmartRestSerializerError; +use csv::{QuoteStyle, WriterBuilder}; +use json_sm::{SoftwareOperationStatus, SoftwareUpdateResponse}; +use serde::{Deserialize, Serialize, Serializer}; + +type SmartRest = String; + +#[derive(Debug)] +pub enum CumulocitySupportedOperations { + C8ySoftwareUpdate, + C8yLogFileRequest, +} + +impl From<CumulocitySupportedOperations> for &'static str { + fn from(op: CumulocitySupportedOperations) -> Self { + match op { + CumulocitySupportedOperations::C8ySoftwareUpdate => "c8y_SoftwareUpdate", + CumulocitySupportedOperations::C8yLogFileRequest => "c8y_LogfileRequest", + } + } +} + +pub trait SmartRestSerializer<'a> +where + Self: Serialize, +{ + fn to_smartrest(&self) -> Result<SmartRest, SmartRestSerializerError> { + serialize_smartrest(self) + } +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct SmartRestSetSupportedLogType { + pub message_id: &'static str, + pub supported_operations: Vec<&'static str>, +} + +impl Default for SmartRestSetSupportedLogType { + fn default() -> Self { + Self { + message_id: "118", + supported_operations: vec!["software-management".into()], + } + } +} + +impl<'a> SmartRestSerializer<'a> for SmartRestSetSupportedLogType {} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct SmartRestSetSupportedOperations { + pub message_id: &'static str, + pub supported_operations: Vec<&'static str>, +} + +impl Default for SmartRestSetSupportedOperations { + fn default() -> Self { + Self { + message_id: "114", + supported_operations: vec![ + CumulocitySupportedOperations::C8ySoftwareUpdate.into(), + CumulocitySupportedOperations::C8yLogFileRequest.into(), + ], + } + } +} + +impl<'a> SmartRestSerializer<'a> for SmartRestSetSupportedOperations {} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct SmartRestSoftwareModuleItem { + pub software: String, + pub version: Option<String>, + pub url: Option<String>, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct SmartRestGetPendingOperations { + pub id: &'static str, +} + +impl Default for SmartRestGetPendingOperations { + fn default() -> Self { + Self { id: "500" } + } +} + +impl<'a> SmartRestSerializer<'a> for SmartRestGetPendingOperations {} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct SmartRestSetOperationToExecuting { + pub message_id: &'static str, + pub operation: &'static str, +} + +impl SmartRestSetOperationToExecuting { + pub fn new(operation: CumulocitySupportedOperations) -> Self { + Self { + message_id: "501", + operation: operation.into(), + } + } + + pub fn from_thin_edge_json( + response: SoftwareUpdateResponse, + ) -> Result<Self, SmartRestSerializerError> { + match response.status() { + SoftwareOperationStatus::Executing => { + Ok(Self::new(CumulocitySupportedOperations::C8ySoftwareUpdate)) + } + _ => Err(SmartRestSerializerError::UnsupportedOperationStatus { response }), + } + } +} + +impl<'a> SmartRestSerializer<'a> for SmartRestSetOperationToExecuting {} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct SmartRestSetOperationToSuccessful { + pub message_id: &'static str, + pub operation: &'static str, + pub operation_parameter: Option<String>, +} + +impl SmartRestSetOperationToSuccessful { + pub fn new(operation: CumulocitySupportedOperations) -> Self { + Self { + message_id: "503", + operation: operation.into(), + operation_parameter: None, + } + } + + pub fn with_response_parameter(self, response_parameter: &str) -> Self { + Self { + operation_parameter: Some(response_parameter.into()), + ..self + } + } + + pub fn from_thin_edge_json( + response: SoftwareUpdateResponse, + ) -> Result<Self, SmartRestSerializerError> { + match response.status() { + SoftwareOperationStatus::Successful => { + Ok(Self::new(CumulocitySupportedOperations::C8ySoftwareUpdate)) + } + _ => Err(SmartRestSerializerError::UnsupportedOperationStatus { response }), + } + } +} + +impl<'a> SmartRestSerializer<'a> for SmartRestSetOperationToSuccessful {} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct SmartRestSetOperationToFailed { + pub message_id: &'static str, + pub operation: &'static str, + #[serde(serialize_with = "reason_to_string_with_quotes")] + pub reason: String, +} + +impl SmartRestSetOperationToFailed { + fn new(operation: CumulocitySupportedOperations, reason: String) -> Self { + Self { + message_id: "502", + operation: operation.into(), + reason, + } + } + + pub fn from_thin_edge_json( + response: SoftwareUpdateResponse, + ) -> Result<Self, SmartRestSerializerError> { + match &response.status() { + SoftwareOperationStatus::Failed => Ok(Self::new( + CumulocitySupportedOperations::C8ySoftwareUpdate, + response.error().unwrap_or_else(|| "".to_string()), + )), + _ => Err(SmartRestSerializerError::UnsupportedOperationStatus { response }), + } + } +} + +impl<'a> SmartRestSerializer<'a> for SmartRestSetOperationToFailed {} + +fn reason_to_string_with_quotes<S>(reason: &str, serializer: S) -> Result<S::Ok, S::Error> +where + S: Serializer, +{ + let s = format!("\"{}\"", reason); + serializer.serialize_str(&s) +} + +fn serialize_smartrest<S: Serialize>(record: S) -> Result<String, SmartRestSerializerError> { + let mut wtr = WriterBuilder::new() + .has_headers(false) + .quote_style(QuoteStyle::Never) + .double_quote(false) + .from_writer(vec![]); + wtr.serialize(record)?; + let csv = String::from_utf8(wtr.into_inner()?)?; + Ok(csv) +} + +#[cfg(test)] +mod tests { + use super::*; + use json_sm::*; + + #[test] + fn serialize_smartrest_supported_operations() { + let smartrest = SmartRestSetSupportedOperations::default() + .to_smartrest() + .unwrap(); + assert_eq!(smartrest, "114,c8y_SoftwareUpdate,c8y_LogfileRequest\n"); + } + + #[test] + fn serialize_smartrest_get_pending_operations() { + let smartrest = SmartRestGetPendingOperations::default() + .to_smartrest() + .unwrap(); + assert_eq!(smartrest, "500\n"); + } + + #[test] + fn serialize_smartrest_set_operation_to_executing() { + let smartrest = + SmartRestSetOperationToExecuting::new(CumulocitySupportedOperations::C8ySoftwareUpdate) + .to_smartrest() + .unwrap(); + assert_eq!(smartrest, "501,c8y_SoftwareUpdate\n"); + } + + #[test] + fn from_thin_edge_json_to_smartrest_set_operation_to_executing() { + let json_response = r#"{ + "id": "123", + "status": "executing" + }"#; + let response = SoftwareUpdateResponse::from_json(json_response).unwrap(); + let smartrest_obj = + SmartRestSetOperationToExecuting::from_thin_edge_json(response).unwrap(); + + let expected_smartrest_obj = SmartRestSetOperationToExecuting { + message_id: "501", + operation: "c8y_SoftwareUpdate", + }; + assert_eq!(smartrest_obj, expected_smartrest_obj); + } + + #[test] + fn serialize_smartrest_set_operation_to_successful() { + let smartrest = SmartRestSetOperationToSuccessful::new( + CumulocitySupportedOperations::C8ySoftwareUpdate, + ) + .to_smartrest() + .unwrap(); + assert_eq!(smartrest, "503,c8y_SoftwareUpdate,\n"); + } + + #[test] + fn from_thin_edge_json_to_smartrest_set_operation_to_successful() { + let json_response = r#"{ + "id":"1", + "status":"successful", + "currentSoftwareList":[] + }"#; + let response = SoftwareUpdateResponse::from_json(json_response).unwrap(); + let smartrest_obj = + SmartRestSetOperationToSuccessful::from_thin_edge_json(response).unwrap(); + + let expected_smartrest_obj = SmartRestSetOperationToSuccessful { + message_id: "503", + operation: "c8y_SoftwareUpdate", + operation_parameter: None, + }; + assert_eq!(smartrest_obj, expected_smartrest_obj); + } + + #[test] + fn serialize_smartrest_set_operation_to_failed() { + let smartrest = SmartRestSetOperationToFailed::new( + CumulocitySupportedOperations::C8ySoftwareUpdate, + "Failed due to permission.".into(), + ) + .to_smartrest() + .unwrap(); + assert_eq!( + smartrest, + "502,c8y_SoftwareUpdate,\"Failed due to permission.\"\n" + ); + } + + #[test] + fn serialize_smartrest_set_operation_to_failed_with_comma_reason() { + let smartrest = SmartRestSetOperationToFailed::new( + CumulocitySupportedOperations::C8ySoftwareUpdate, |