Skip to main content

homeassistant_cli/api/
entities.rs

1use crate::api::{EntityState, HaClient, HaError};
2
3pub async fn get_state(client: &HaClient, entity_id: &str) -> Result<EntityState, HaError> {
4    let resp = client
5        .get(&format!("/api/states/{entity_id}"))
6        .send()
7        .await?;
8    match resp.status().as_u16() {
9        200 => Ok(resp.json().await?),
10        401 | 403 => Err(HaError::Auth(format!("Unauthorized accessing {entity_id}"))),
11        404 => Err(HaError::NotFound(format!("Entity '{entity_id}' not found"))),
12        status => Err(HaError::Api {
13            status,
14            message: resp.text().await.unwrap_or_default(),
15        }),
16    }
17}
18
19pub async fn list_states(client: &HaClient) -> Result<Vec<EntityState>, HaError> {
20    let resp = client.get("/api/states").send().await?;
21    match resp.status().as_u16() {
22        200 => Ok(resp.json().await?),
23        401 | 403 => Err(HaError::Auth("Unauthorized".into())),
24        status => Err(HaError::Api {
25            status,
26            message: resp.text().await.unwrap_or_default(),
27        }),
28    }
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34    use crate::api::HaClient;
35    use wiremock::matchers::{header, method, path};
36    use wiremock::{Mock, MockServer, ResponseTemplate};
37
38    async fn mock_client(server: &MockServer) -> HaClient {
39        HaClient::new(server.uri(), "test-token")
40    }
41
42    #[tokio::test]
43    async fn get_state_returns_entity() {
44        let server = MockServer::start().await;
45        Mock::given(method("GET"))
46            .and(path("/api/states/light.living_room"))
47            .and(header("Authorization", "Bearer test-token"))
48            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
49                "entity_id": "light.living_room",
50                "state": "on",
51                "attributes": {"brightness": 128},
52                "last_changed": "2026-01-01T00:00:00Z",
53                "last_updated": "2026-01-01T00:00:00Z"
54            })))
55            .mount(&server)
56            .await;
57
58        let client = mock_client(&server).await;
59        let state = get_state(&client, "light.living_room").await.unwrap();
60        assert_eq!(state.entity_id, "light.living_room");
61        assert_eq!(state.state, "on");
62    }
63
64    #[tokio::test]
65    async fn get_state_returns_not_found_on_404() {
66        let server = MockServer::start().await;
67        Mock::given(method("GET"))
68            .and(path("/api/states/light.missing"))
69            .respond_with(ResponseTemplate::new(404))
70            .mount(&server)
71            .await;
72
73        let client = mock_client(&server).await;
74        let result = get_state(&client, "light.missing").await;
75        assert!(matches!(result, Err(crate::api::HaError::NotFound(_))));
76    }
77
78    #[tokio::test]
79    async fn list_states_returns_all_entities() {
80        let server = MockServer::start().await;
81        Mock::given(method("GET"))
82            .and(path("/api/states"))
83            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
84                {"entity_id": "light.x", "state": "on", "attributes": {}, "last_changed": "2026-01-01T00:00:00Z", "last_updated": "2026-01-01T00:00:00Z"},
85                {"entity_id": "switch.y", "state": "off", "attributes": {}, "last_changed": "2026-01-01T00:00:00Z", "last_updated": "2026-01-01T00:00:00Z"}
86            ])))
87            .mount(&server)
88            .await;
89
90        let client = mock_client(&server).await;
91        let states = list_states(&client).await.unwrap();
92        assert_eq!(states.len(), 2);
93    }
94
95    #[tokio::test]
96    async fn get_state_returns_auth_error_on_401() {
97        let server = MockServer::start().await;
98        Mock::given(method("GET"))
99            .and(path("/api/states/light.x"))
100            .respond_with(ResponseTemplate::new(401))
101            .mount(&server)
102            .await;
103
104        let client = mock_client(&server).await;
105        let result = get_state(&client, "light.x").await;
106        assert!(matches!(result, Err(crate::api::HaError::Auth(_))));
107    }
108}