1use crate::{
10 AzureHttpClient, Result,
11 ops::loganalytics::LoganalyticsOps,
12 types::loganalytics::{
13 LogQueryBody, LogQueryResult, SavedSearchListResult, Workspace, WorkspaceCreateRequest,
14 WorkspaceListResult,
15 },
16};
17
18pub struct LogAnalyticsClient<'a> {
20 ops: LoganalyticsOps<'a>,
21 client: &'a AzureHttpClient,
22}
23
24impl<'a> LogAnalyticsClient<'a> {
25 pub(crate) fn new(client: &'a AzureHttpClient) -> Self {
27 Self {
28 ops: LoganalyticsOps::new(client),
29 client,
30 }
31 }
32
33 pub async fn list_workspaces(&self) -> Result<WorkspaceListResult> {
35 self.ops
36 .list_workspaces(self.client.subscription_id())
37 .await
38 }
39
40 pub async fn get_workspace(
42 &self,
43 resource_group_name: &str,
44 workspace_name: &str,
45 ) -> Result<Workspace> {
46 self.ops
47 .get_workspace(
48 self.client.subscription_id(),
49 resource_group_name,
50 workspace_name,
51 )
52 .await
53 }
54
55 pub async fn create_workspace(
57 &self,
58 resource_group_name: &str,
59 workspace_name: &str,
60 body: &WorkspaceCreateRequest,
61 ) -> Result<Workspace> {
62 self.ops
63 .create_workspace(
64 self.client.subscription_id(),
65 resource_group_name,
66 workspace_name,
67 body,
68 )
69 .await
70 }
71
72 pub async fn delete_workspace(
74 &self,
75 resource_group_name: &str,
76 workspace_name: &str,
77 ) -> Result<()> {
78 self.ops
79 .delete_workspace(
80 self.client.subscription_id(),
81 resource_group_name,
82 workspace_name,
83 )
84 .await
85 }
86
87 pub async fn query_logs(
89 &self,
90 resource_group_name: &str,
91 workspace_name: &str,
92 body: &LogQueryBody,
93 ) -> Result<LogQueryResult> {
94 self.ops
95 .query_logs(
96 self.client.subscription_id(),
97 resource_group_name,
98 workspace_name,
99 body,
100 )
101 .await
102 }
103
104 pub async fn list_saved_searches(
106 &self,
107 resource_group_name: &str,
108 workspace_name: &str,
109 ) -> Result<SavedSearchListResult> {
110 self.ops
111 .list_saved_searches(
112 self.client.subscription_id(),
113 resource_group_name,
114 workspace_name,
115 )
116 .await
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::MockClient;
124 use crate::types::loganalytics::WorkspaceCreateRequest;
125
126 const SUB_ID: &str = "test-subscription-id";
127 const RG: &str = "cloud-lite-test-rg";
128 const WS_NAME: &str = "cloud-lite-test-ralph-workspace";
129
130 fn make_client(mock: MockClient) -> AzureHttpClient {
131 AzureHttpClient::from_mock(mock)
132 }
133
134 fn workspace_json() -> serde_json::Value {
135 serde_json::json!({
136 "id": format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.OperationalInsights/workspaces/{WS_NAME}"),
137 "name": WS_NAME,
138 "type": "Microsoft.OperationalInsights/workspaces",
139 "location": "eastus",
140 "properties": {
141 "customerId": "aae30729-30f7-4237-aec7-59447782acbb",
142 "provisioningState": "Succeeded",
143 "retentionInDays": 30,
144 "sku": { "name": "PerGB2018" }
145 }
146 })
147 }
148
149 #[tokio::test]
150 async fn list_workspaces_returns_list() {
151 let mut mock = MockClient::new();
152 mock.expect_get(&format!(
153 "/subscriptions/{SUB_ID}/providers/Microsoft.OperationalInsights/workspaces"
154 ))
155 .returning_json(serde_json::json!({ "value": [workspace_json()] }));
156 let client = make_client(mock);
157 let result = client
158 .log_analytics()
159 .list_workspaces()
160 .await
161 .expect("list_workspaces failed");
162 assert_eq!(result.value.len(), 1);
163 assert_eq!(result.value[0].name.as_deref(), Some(WS_NAME));
164 }
165
166 #[tokio::test]
167 async fn get_workspace_deserializes_properties() {
168 let mut mock = MockClient::new();
169 mock.expect_get(
170 &format!("/subscriptions/{SUB_ID}/resourcegroups/{RG}/providers/Microsoft.OperationalInsights/workspaces/{WS_NAME}"),
171 )
172 .returning_json(workspace_json());
173 let client = make_client(mock);
174 let ws = client
175 .log_analytics()
176 .get_workspace(RG, WS_NAME)
177 .await
178 .expect("get_workspace failed");
179 assert_eq!(ws.name.as_deref(), Some(WS_NAME));
180 let props = ws.properties.as_ref().unwrap();
181 assert_eq!(
182 props.customer_id.as_deref(),
183 Some("aae30729-30f7-4237-aec7-59447782acbb")
184 );
185 assert_eq!(props.provisioning_state.as_deref(), Some("Succeeded"));
186 assert_eq!(props.retention_in_days, Some(30));
187 }
188
189 #[tokio::test]
190 async fn create_workspace_returns_workspace() {
191 let mut mock = MockClient::new();
192 mock.expect_put(
193 &format!("/subscriptions/{SUB_ID}/resourcegroups/{RG}/providers/Microsoft.OperationalInsights/workspaces/{WS_NAME}"),
194 )
195 .returning_json(workspace_json());
196 let client = make_client(mock);
197 let body = WorkspaceCreateRequest {
198 location: "eastus".into(),
199 ..Default::default()
200 };
201 let ws = client
202 .log_analytics()
203 .create_workspace(RG, WS_NAME, &body)
204 .await
205 .expect("create_workspace failed");
206 assert_eq!(ws.name.as_deref(), Some(WS_NAME));
207 }
208
209 #[tokio::test]
210 async fn delete_workspace_succeeds() {
211 let mut mock = MockClient::new();
212 mock.expect_delete(
213 &format!("/subscriptions/{SUB_ID}/resourcegroups/{RG}/providers/Microsoft.OperationalInsights/workspaces/{WS_NAME}"),
214 )
215 .returning_json(serde_json::json!({}));
216 let client = make_client(mock);
217 client
218 .log_analytics()
219 .delete_workspace(RG, WS_NAME)
220 .await
221 .expect("delete_workspace failed");
222 }
223
224 #[tokio::test]
225 async fn query_logs_returns_tables() {
226 let mut mock = MockClient::new();
227 mock.expect_post(
228 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.OperationalInsights/workspaces/{WS_NAME}/query"),
229 )
230 .returning_json(serde_json::json!({
231 "tables": [
232 {
233 "name": "PrimaryResult",
234 "columns": [
235 { "name": "TimeGenerated", "type": "datetime" },
236 { "name": "Category", "type": "string" }
237 ],
238 "rows": [
239 ["2024-01-01T00:00:00Z", "Administrative"]
240 ]
241 }
242 ]
243 }));
244 let client = make_client(mock);
245 let body = LogQueryBody {
246 query: "AzureActivity | limit 5".into(),
247 timespan: Some("PT1H".into()),
248 ..Default::default()
249 };
250 let result = client
251 .log_analytics()
252 .query_logs(RG, WS_NAME, &body)
253 .await
254 .expect("query_logs failed");
255 assert_eq!(result.tables.len(), 1);
256 assert_eq!(result.tables[0].name.as_deref(), Some("PrimaryResult"));
257 assert_eq!(result.tables[0].columns.len(), 2);
258 assert_eq!(
259 result.tables[0].columns[0].name.as_deref(),
260 Some("TimeGenerated")
261 );
262 assert_eq!(result.tables[0].rows.len(), 1);
263 }
264
265 #[tokio::test]
266 async fn list_saved_searches_returns_list() {
267 let mut mock = MockClient::new();
268 mock.expect_get(
269 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.OperationalInsights/workspaces/{WS_NAME}/savedSearches"),
270 )
271 .returning_json(serde_json::json!({ "value": [] }));
272 let client = make_client(mock);
273 let result = client
274 .log_analytics()
275 .list_saved_searches(RG, WS_NAME)
276 .await
277 .expect("list_saved_searches failed");
278 assert_eq!(result.value.len(), 0);
279 }
280}