matroskin 0.1.0

whatsminer api client lib
Documentation
//! Implement `set.miner.pools` command
//!
//! Configures three pool entries using encrypted parameters.
//!
//! - Command: [SetMinerPools]
//! - ApiDoc: <https://apidoc.whatsminer.com/#api-Miner-btminer_set_pools>
use core::str;

use serde::{Deserialize, Serialize};

use crate::{
    command::{Command, CommandLevel},
    error::Result,
    redacted::Redacted,
};

/// Individual pool configuration.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PoolConfig {
    /// Stratum url
    pub pool: String,
    /// Worker username
    pub worker: String,
    /// Password
    pub passwd: Redacted<String>,
}

impl PoolConfig {
    pub fn new(
        pool: impl Into<String>,
        worker: impl Into<String>,
        passwd: impl Into<String>,
    ) -> Self {
        Self {
            pool: pool.into(),
            worker: worker.into(),
            passwd: Redacted(passwd.into()),
        }
    }
}

/// This command represents the `set.miner.pools` operation.
///
/// It is used to set the miner's pools for the miner.
///
/// - ApiDoc: <https://apidoc.whatsminer.com/#api-Miner-btminer_set_pools>
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct SetMinerPools {
    pub pools: [PoolConfig; 3],
}

impl SetMinerPools {
    /// Create a new `set.miner.pools` command with 3 pool configurations.
    pub fn new(pools: [PoolConfig; 3]) -> Self {
        Self { pools }
    }
}

impl From<[PoolConfig; 3]> for SetMinerPools {
    fn from(pools: [PoolConfig; 3]) -> Self {
        Self { pools }
    }
}

impl Command for SetMinerPools {
    type Response = String;
    const CMD_NAME: &'static str = "set.miner.pools";
    const LEVEL: CommandLevel = CommandLevel::Encrypted;
    fn params(&self) -> Result<Option<impl serde::Serialize>> {
        Ok(Some(&self.pools))
    }
}
#[cfg(test)]
mod tests {
    use crate::account::Account;
    use crate::auth_data::AuthData;

    use super::*;

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

    #[test]
    fn request_with_pools_is_encrypted() {
        let pools = [
            PoolConfig {
                pool: "stratum+tcp://1.1.1.1:3333".to_string(),
                worker: "test.worker".to_string(),
                passwd: Redacted("x".to_string()),
            },
            PoolConfig::default(),
            PoolConfig::default(),
        ];
        let cmd = SetMinerPools::new(pools);

        assert!(cmd.is_secured());
        assert!(cmd.is_encrypted());

        let auth = AuthData::new::<SetMinerPools>(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.miner.pools");
        assert_eq!(json["account"], "super");
        assert!(json["ts"].as_u64().unwrap() > 0);
        assert_eq!(json["token"].as_str().unwrap().len(), 8);
        // param should be encrypted (base64 string), not raw JSON
        let param = json["param"].as_str().unwrap();
        assert!(!param.contains("stratum"));
        assert!(!param.contains("worker"));
        // valid base64
        assert!(!base64_light::base64_decode(param).is_empty());
    }

    #[test]
    fn pool_credentials_are_redacted_but_serialize_for_encryption() {
        let pool = PoolConfig::new("stratum+tcp://localhost:3333", "worker", "pool-secret");
        assert!(!format!("{pool:?}").contains("pool-secret"));
        let json = serde_json::to_value(&pool).unwrap();
        assert_eq!(json["passwd"], "pool-secret");
    }

    #[test]
    fn request_without_auth_returns_error() {
        let cmd = SetMinerPools::default();
        let result = cmd.encode(None);
        assert!(result.is_err());
    }

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