melcloud_api/api/types/
session.rs

1// A Session is where everything begins.  My plan (for now) is that we go via the session
2// for everything, but we will see how the API develops.
3use crate::api::types::Config;
4use crate::api::errors;
5use reqwest;
6use crate::api::json::{MelcloudLoginResponse};
7use crate::api::types::devices::Devices;
8
9
10#[derive(Debug)]
11pub struct Session {
12    pub config: Config,
13    pub api_key: String
14}
15
16impl Session {
17    /// Starts a new session
18    pub fn start(config: Config) -> Result<Session, errors::ApiError> {
19        let request_url = format!("{api_base}/Login/ClientLogin", api_base = &config.api_url);
20        let post_body = format!("{{\"Email\":\"{email}\",\"Password\":\"{password}\",\"Language\":0,\"AppVersion\":\"1.18.5.1\",\"Persist\":true,\"CaptchaResponse\":null}}", email = config.api_username, password = config.api_password);
21        let result = reqwest::Client::new()
22            .post(&request_url)
23            .body(post_body)
24            .header("Accept", "application/json")
25            .header("Content-Type", "application/json")
26            .send();
27        match result {
28            Ok(mut resp) => {
29                match resp.json() {
30                    Ok(json) => Session::parse_new_session_response(config, json),
31                    Err(err) => {
32                        println!("Err is {:?}", err);
33                        Err(errors::ApiError::InvalidLoginResponse)
34                    }
35                }
36            },
37            Err(err) => Err(errors::ApiError::LoginFailure)
38        }
39    }
40
41    pub fn devices(&self) -> Devices {
42        Devices::new(self)
43    }
44
45    fn parse_new_session_response(config: Config, message: MelcloudLoginResponse) -> Result<Session, errors::ApiError> {
46        match message {
47            MelcloudLoginResponse::Success {login_data} => {
48                Ok(Session {
49                    config: config,
50                    api_key: login_data.context_key
51                })
52            },
53            MelcloudLoginResponse::AccessDenied {error_id} => {
54                Err(errors::ApiError::LoginFailure)
55            }
56        }
57    }
58
59}
60
61#[cfg(test)]
62mod tests {
63    use mockito::{mock, Matcher};
64    use crate::api::types::Config;
65    use crate::api::types::Session;
66    use crate::api::errors;
67
68    #[test]
69    fn test_start_session() {
70        // Arrange - Create a config and setup a mock server
71        let config = Config::new(&mockito::server_url(), "testuser", "testpassword");
72        let m = mock("POST", "/Login/ClientLogin")
73            .with_status(200)
74            .with_body("{\"ErrorId\":null,\"ErrorMessage\":null,\"LoginStatus\":0,\"UserId\":0,\"RandomKey\":null,\"AppVersionAnnouncement\":null,\"LoginData\":{\"ContextKey\":\"C71933C57FB04358B6750ED77D79AA\",\"Client\":128538,\"Terms\":1199,\"AL\":1,\"ML\":0,\"CMI\":true,\"IsStaff\":false,\"CUTF\":false,\"CAA\":false,\"ReceiveCountryNotifications\":false,\"ReceiveAllNotifications\":false,\"CACA\":false,\"CAGA\":false,\"MaximumDevices\":10,\"ShowDiagnostics\":false,\"Language\":0,\"Country\":237,\"RealClient\":0,\"Name\":\"Gary Taylor\",\"UseFahrenheit\":false,\"Duration\":525600,\"Expiry\":\"2020-10-14T13:54:42.653\",\"CMSC\":false,\"PartnerApplicationVersion\":null,\"EmailSettingsReminderShown\":true,\"EmailUnitErrors\":1,\"EmailCommsErrors\":1,\"IsImpersonated\":false,\"LanguageCode\":\"en\",\"CountryName\":\"United Kingdom\",\"CurrencySymbol\":\"£\",\"SupportEmailAddress\":\"melcloud.support@meuk.mee.com \",\"DateSeperator\":\"/\",\"TimeSeperator\":\":\",\"AtwLogoFile\":\"ecodan_logo.png\",\"DECCReport\":true,\"CSVReport1min\":true,\"HidePresetPanel\":false,\"EmailSettingsReminderRequired\":false,\"TermsText\":null,\"MapView\":false,\"MapZoom\":0,\"MapLongitude\":-133.082129213169600,\"MapLatitude\":52.621242998717900},\"ListPendingInvite\":[],\"ListOwnershipChangeRequest\":[],\"ListPendingAnnouncement\":[],\"LoginMinutes\":0,\"LoginAttempts\":0}")
75            .create();
76
77        // Act - Start the session
78        let result = Session::start(config);
79
80        // Assert - Make sure the result contains the api key
81        m.assert();
82        match result {
83            Ok(session) => assert!(session.api_key == "C71933C57FB04358B6750ED77D79AA", "The api key was wrong"),
84            Err(err) => assert!(false, "Something went wrong"),
85        }
86    }
87
88    #[test]
89    fn test_start_session_wrong_credentials() {
90        // Arrange - Create a config and setup a mock server
91        let config = Config::new(&mockito::server_url(), "testuser", "testpassword");
92        let m = mock("POST", "/Login/ClientLogin")
93            .with_status(200)
94            .with_body("{\"ErrorId\":1,\"ErrorMessage\":null,\"LoginStatus\":0,\"UserId\":0,\"RandomKey\":null,\"AppVersionAnnouncement\":null,\"LoginData\":null,\"ListPendingInvite\":null,\"ListOwnershipChangeRequest\":null,\"ListPendingAnnouncement\":null,\"LoginMinutes\":0,\"LoginAttempts\":1}")
95            .create();
96
97        // Act - Start the session
98        let result = Session::start(config);
99
100        // Assert - Make sure the result contains the api key
101        m.assert();
102        match result {
103            Ok(session) => assert!(false, "Must return an error"),
104            Err(errors::ApiError::LoginFailure) => assert!(true),
105            Err(_) => assert!(false, "Must return a Login Failure")
106        }
107    }
108}