matroskin 0.1.0

whatsminer api client lib
Documentation
//! Implement `set.user.change_passwd` command
//!
//! Changes the password for a specified user account.
//! The param field is AES-encrypted before being sent over the wire.
//!
//! - Command: [SetUserChangePasswd]
//! - ApiDoc: <https://apidoc.whatsminer.com/#api-User-btminer_set_user_change_passwd>
use core::str;

use serde::Serialize;

use crate::{
    account::Users,
    command::{Command, CommandLevel},
    error::Result,
    permission::PermissionLevel,
    redacted::Redacted,
};

/// This command represents the `set.user.change_passwd` operation.
///
/// Changes the password for a specified user account.
/// The `param` field is AES-encrypted before being sent over the wire.
///
/// - ApiDoc: <https://apidoc.whatsminer.com/#api-User-btminer_set_user_change_passwd>
///
/// # Example
/// ```rust,no_run
/// use matroskin::actor::Actor;
/// use matroskin::account::Account;
/// use matroskin::password::Password;
/// use matroskin::command::set_user_change_passwd::SetUserChangePasswd;
/// use matroskin::account::Users;
/// use matroskin::redacted::Redacted;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let addr = "10.10.10.10:4433";
///     let username = Account::Super;
///     let password = Password::Super;
///
///     let actor = Actor::new(addr, username, password);
///     actor.connect()?;
///
///     let cmd = SetUserChangePasswd::new(
///         Users::User1,
///         Redacted("new_password".to_string()),
///         Redacted("old_password".to_string()),
///     );
///     let response = actor.send(cmd).await?;
///     println!("Response: {:#?}", response);
///
///     Ok(())
/// }
/// ```
#[derive(Debug, Serialize, Clone, PartialEq)]
pub struct SetUserChangePasswd {
    /// Target user account. Valid values: `user1`, `user2`, `user3`.
    pub account: Users,
    #[serde(rename = "new")]
    /// New password.
    pub new_passwd: Redacted<String>,
    #[serde(rename = "old")]
    /// Current password.
    pub old_passwd: Redacted<String>,
}

impl SetUserChangePasswd {
    /// Create a new `set.user.change_passwd` command.
    pub fn new(
        account: Users,
        new_passwd: impl Into<Redacted<String>>,
        old_passwd: impl Into<Redacted<String>>,
    ) -> Self {
        Self {
            account,
            new_passwd: new_passwd.into(),
            old_passwd: old_passwd.into(),
        }
    }
}

impl Command for SetUserChangePasswd {
    type Response = String;
    const CMD_NAME: &'static str = "set.user.change_passwd";
    const LEVEL: CommandLevel = CommandLevel::Encrypted;
    const MINIMAL_PERMISSIONS: PermissionLevel = PermissionLevel::Advanced;

    fn params(&self) -> Result<Option<impl serde::Serialize>> {
        Ok(Some(self))
    }
}

#[cfg(test)]
mod tests {
    use crate::account::Account;
    use crate::auth_data::AuthData;
    use crate::redacted::Redacted;

    use super::*;

    const RESPONSE_JSON: &str =
        r#"{"code":0,"when":1692685512,"msg":"ok","desc":"set.user.change_passwd"}"#;

    #[test]
    fn request_has_correct_structure() {
        let cmd = SetUserChangePasswd::new(
            Users::User1,
            Redacted("new_pass".to_string()),
            Redacted("old_pass".to_string()),
        );
        assert!(cmd.is_secured());
        assert!(cmd.is_encrypted());

        let params = cmd.params().unwrap().unwrap();
        let parsed = serde_json::to_value(params).unwrap();
        assert_eq!(parsed["account"], "user1");
        assert_eq!(parsed["new"], "new_pass");
        assert_eq!(parsed["old"], "old_pass");
    }

    #[test]
    fn encode_with_auth_encrypts_params() {
        let cmd = SetUserChangePasswd::new(
            Users::User1,
            Redacted("new_pass".to_string()),
            Redacted("old_pass".to_string()),
        );
        let auth = AuthData::new::<SetUserChangePasswd>(Account::Super, "pass", "salt").unwrap();
        let encoded = cmd.encode(Some(auth)).unwrap();
        let json: serde_json::Value = serde_json::from_slice(&encoded.0).unwrap();

        assert_eq!(json["cmd"], "set.user.change_passwd");
        assert_eq!(json["account"], "super");
        assert!(json["ts"].as_u64().unwrap() > 0);
        assert_eq!(json["token"].as_str().unwrap().len(), 8);
        let param = json["param"].as_str().unwrap();
        assert!(!param.contains("user1"), "param should be encrypted");
        assert!(!param.contains("new_pass"), "param should be encrypted");
    }

    #[test]
    fn deserialize_response_fixture() {
        let response = SetUserChangePasswd::parse_response(RESPONSE_JSON.as_bytes()).unwrap();
        assert!(response.code.is_success());
        assert_eq!(response.desc, "set.user.change_passwd");
        assert_eq!(response.when, 1692685512);
        assert_eq!(response.msg, "ok");
    }
}