1use crate::{
8 AzureHttpClient, Result,
9 ops::functions::FunctionsOps,
10 types::functions::{
11 AppSettingsResult, AppSettingsUpdateRequest, Function, FunctionApp,
12 FunctionAppCreateRequest, FunctionAppListResult, FunctionListResult,
13 },
14};
15
16pub struct FunctionsClient<'a> {
21 ops: FunctionsOps<'a>,
22 client: &'a AzureHttpClient,
23}
24
25impl<'a> FunctionsClient<'a> {
26 pub(crate) fn new(client: &'a AzureHttpClient) -> Self {
28 Self {
29 ops: FunctionsOps::new(client),
30 client,
31 }
32 }
33
34 pub async fn list_function_apps(&self) -> Result<FunctionAppListResult> {
38 self.ops
39 .list_function_apps(self.client.subscription_id())
40 .await
41 }
42
43 pub async fn list_function_apps_by_resource_group(
45 &self,
46 resource_group_name: &str,
47 ) -> Result<FunctionAppListResult> {
48 self.ops
49 .list_function_apps_by_resource_group(
50 self.client.subscription_id(),
51 resource_group_name,
52 )
53 .await
54 }
55
56 pub async fn get_function_app(
58 &self,
59 resource_group_name: &str,
60 name: &str,
61 ) -> Result<FunctionApp> {
62 self.ops
63 .get_function_app(self.client.subscription_id(), resource_group_name, name)
64 .await
65 }
66
67 pub async fn create_function_app(
69 &self,
70 resource_group_name: &str,
71 name: &str,
72 body: &FunctionAppCreateRequest,
73 ) -> Result<FunctionApp> {
74 self.ops
75 .create_function_app(
76 self.client.subscription_id(),
77 resource_group_name,
78 name,
79 body,
80 )
81 .await
82 }
83
84 pub async fn delete_function_app(&self, resource_group_name: &str, name: &str) -> Result<()> {
86 self.ops
87 .delete_function_app(self.client.subscription_id(), resource_group_name, name)
88 .await
89 }
90
91 pub async fn list_functions(
95 &self,
96 resource_group_name: &str,
97 name: &str,
98 ) -> Result<FunctionListResult> {
99 self.ops
100 .list_functions(self.client.subscription_id(), resource_group_name, name)
101 .await
102 }
103
104 pub async fn get_function(
106 &self,
107 resource_group_name: &str,
108 name: &str,
109 function_name: &str,
110 ) -> Result<Function> {
111 self.ops
112 .get_function(
113 self.client.subscription_id(),
114 resource_group_name,
115 name,
116 function_name,
117 )
118 .await
119 }
120
121 pub async fn list_app_settings(
125 &self,
126 resource_group_name: &str,
127 name: &str,
128 ) -> Result<AppSettingsResult> {
129 self.ops
130 .list_app_settings(self.client.subscription_id(), resource_group_name, name)
131 .await
132 }
133
134 pub async fn update_app_settings(
136 &self,
137 resource_group_name: &str,
138 name: &str,
139 body: &AppSettingsUpdateRequest,
140 ) -> Result<AppSettingsResult> {
141 self.ops
142 .update_app_settings(
143 self.client.subscription_id(),
144 resource_group_name,
145 name,
146 body,
147 )
148 .await
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::MockClient;
156
157 const SUB_ID: &str = "test-subscription-id";
158 const RG: &str = "test-rg";
159 const APP: &str = "cloud-lite-test-func-app";
160 const FUNC: &str = "MyFunction";
161
162 fn make_client(mock: MockClient) -> AzureHttpClient {
163 AzureHttpClient::from_mock(mock)
164 }
165
166 fn app_json() -> serde_json::Value {
167 serde_json::json!({
168 "id": format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}"),
169 "name": APP,
170 "type": "Microsoft.Web/sites",
171 "kind": "functionapp,linux",
172 "location": "eastus",
173 "properties": {
174 "state": "Running",
175 "hostNames": ["cloud-lite-test-func-app.azurewebsites.net"],
176 "defaultHostName": "cloud-lite-test-func-app.azurewebsites.net",
177 "resourceGroup": RG,
178 "serverFarmId": format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/serverfarms/EastUSLinuxDynamicPlan"),
179 "siteConfig": {
180 "numberOfWorkers": 1,
181 "linuxFxVersion": "Python|3.11"
182 }
183 }
184 })
185 }
186
187 fn function_json() -> serde_json::Value {
188 serde_json::json!({
189 "id": format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}/functions/{FUNC}"),
190 "name": format!("{APP}/{FUNC}"),
191 "type": "Microsoft.Web/sites/functions",
192 "properties": {
193 "name": FUNC,
194 "functionAppId": format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}"),
195 "isDisabled": false
196 }
197 })
198 }
199
200 fn settings_json() -> serde_json::Value {
201 serde_json::json!({
202 "id": format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}/config/appsettings"),
203 "name": "appsettings",
204 "type": "Microsoft.Web/sites/config",
205 "properties": {
206 "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;...",
207 "FUNCTIONS_WORKER_RUNTIME": "python"
208 }
209 })
210 }
211
212 #[tokio::test]
213 async fn list_function_apps_returns_list() {
214 let mut mock = MockClient::new();
215 mock.expect_get(&format!(
216 "/subscriptions/{SUB_ID}/providers/Microsoft.Web/sites"
217 ))
218 .returning_json(serde_json::json!({ "value": [app_json()] }));
219 let client = make_client(mock);
220 let result = client
221 .functions()
222 .list_function_apps()
223 .await
224 .expect("list_function_apps failed");
225 assert_eq!(result.value.len(), 1);
226 let app = &result.value[0];
227 assert_eq!(app.name.as_deref(), Some(APP));
228 let props = app.properties.as_ref().unwrap();
229 assert_eq!(props.state.as_deref(), Some("Running"));
230 }
231
232 #[tokio::test]
233 async fn list_function_apps_by_resource_group_returns_list() {
234 let mut mock = MockClient::new();
235 mock.expect_get(&format!(
236 "/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites"
237 ))
238 .returning_json(serde_json::json!({ "value": [app_json()] }));
239 let client = make_client(mock);
240 let result = client
241 .functions()
242 .list_function_apps_by_resource_group(RG)
243 .await
244 .expect("list_function_apps_by_resource_group failed");
245 assert_eq!(result.value.len(), 1);
246 }
247
248 #[tokio::test]
249 async fn get_function_app_deserializes_properties() {
250 let mut mock = MockClient::new();
251 mock.expect_get(&format!(
252 "/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}"
253 ))
254 .returning_json(app_json());
255 let client = make_client(mock);
256 let app = client
257 .functions()
258 .get_function_app(RG, APP)
259 .await
260 .expect("get_function_app failed");
261 assert_eq!(app.name.as_deref(), Some(APP));
262 let props = app.properties.as_ref().unwrap();
263 assert_eq!(
264 props.default_host_name.as_deref(),
265 Some("cloud-lite-test-func-app.azurewebsites.net")
266 );
267 let config = props.site_config.as_ref().unwrap();
268 assert_eq!(config.linux_fx_version.as_deref(), Some("Python|3.11"));
269 }
270
271 #[tokio::test]
272 async fn create_function_app_sends_body() {
273 let mut mock = MockClient::new();
274 mock.expect_put(&format!(
275 "/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}"
276 ))
277 .returning_json(app_json());
278 let client = make_client(mock);
279 let body = FunctionAppCreateRequest {
280 location: "eastus".into(),
281 kind: Some("functionapp,linux".into()),
282 ..Default::default()
283 };
284 let app = client
285 .functions()
286 .create_function_app(RG, APP, &body)
287 .await
288 .expect("create_function_app failed");
289 assert_eq!(app.name.as_deref(), Some(APP));
290 }
291
292 #[tokio::test]
293 async fn delete_function_app_succeeds() {
294 let mut mock = MockClient::new();
295 mock.expect_delete(&format!(
296 "/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}"
297 ))
298 .returning_json(serde_json::json!({}));
299 let client = make_client(mock);
300 client
301 .functions()
302 .delete_function_app(RG, APP)
303 .await
304 .expect("delete_function_app failed");
305 }
306
307 #[tokio::test]
308 async fn list_functions_returns_list() {
309 let mut mock = MockClient::new();
310 mock.expect_get(
311 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}/functions"),
312 )
313 .returning_json(serde_json::json!({ "value": [function_json()] }));
314 let client = make_client(mock);
315 let result = client
316 .functions()
317 .list_functions(RG, APP)
318 .await
319 .expect("list_functions failed");
320 assert_eq!(result.value.len(), 1);
321 let f = &result.value[0];
322 let props = f.properties.as_ref().unwrap();
323 assert_eq!(props.is_disabled, Some(false));
324 }
325
326 #[tokio::test]
327 async fn get_function_deserializes_properties() {
328 let mut mock = MockClient::new();
329 mock.expect_get(
330 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}/functions/{FUNC}"),
331 )
332 .returning_json(function_json());
333 let client = make_client(mock);
334 let f = client
335 .functions()
336 .get_function(RG, APP, FUNC)
337 .await
338 .expect("get_function failed");
339 let props = f.properties.as_ref().unwrap();
340 assert_eq!(props.name.as_deref(), Some(FUNC));
341 }
342
343 #[tokio::test]
344 async fn list_app_settings_returns_settings() {
345 let mut mock = MockClient::new();
346 mock.expect_post(
347 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}/config/appsettings/list"),
348 )
349 .returning_json(settings_json());
350 let client = make_client(mock);
351 let settings = client
352 .functions()
353 .list_app_settings(RG, APP)
354 .await
355 .expect("list_app_settings failed");
356 assert!(settings.properties.contains_key("AzureWebJobsStorage"));
357 assert!(settings.properties.contains_key("FUNCTIONS_WORKER_RUNTIME"));
358 }
359
360 #[tokio::test]
361 async fn update_app_settings_sends_body() {
362 let mut mock = MockClient::new();
363 mock.expect_put(
364 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Web/sites/{APP}/config/appsettings"),
365 )
366 .returning_json(settings_json());
367 let client = make_client(mock);
368 let mut props = std::collections::HashMap::new();
369 props.insert("CLOUD_LITE_TEST".to_string(), "hello".to_string());
370 let body = AppSettingsUpdateRequest { properties: props };
371 let result = client
372 .functions()
373 .update_app_settings(RG, APP, &body)
374 .await
375 .expect("update_app_settings failed");
376 assert_eq!(result.name.as_deref(), Some("appsettings"));
377 }
378}