homeassistant_cli/api/
entities.rs1use 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}