1use crate::{
9 AzureHttpClient, Result,
10 ops::sql::SqlOps,
11 types::sql::{
12 Database, DatabaseCreateRequest, DatabaseListResult, EnableServerAuditingRequest,
13 FirewallRule, FirewallRuleCreateRequest, FirewallRuleListResult, Server,
14 ServerBlobAuditingPolicy, ServerCreateRequest, ServerListResult,
15 },
16};
17
18pub struct SqlClient<'a> {
23 ops: SqlOps<'a>,
24 client: &'a AzureHttpClient,
25}
26
27impl<'a> SqlClient<'a> {
28 pub(crate) fn new(client: &'a AzureHttpClient) -> Self {
30 Self {
31 ops: SqlOps::new(client),
32 client,
33 }
34 }
35
36 pub async fn list_servers(&self) -> Result<ServerListResult> {
40 self.ops.list_servers(self.client.subscription_id()).await
41 }
42
43 pub async fn get_server(&self, resource_group_name: &str, server_name: &str) -> Result<Server> {
45 self.ops
46 .get_server(
47 self.client.subscription_id(),
48 resource_group_name,
49 server_name,
50 )
51 .await
52 }
53
54 pub async fn create_server(
56 &self,
57 resource_group_name: &str,
58 server_name: &str,
59 body: &ServerCreateRequest,
60 ) -> Result<Server> {
61 self.ops
62 .create_server(
63 self.client.subscription_id(),
64 resource_group_name,
65 server_name,
66 body,
67 )
68 .await
69 }
70
71 pub async fn delete_server(&self, resource_group_name: &str, server_name: &str) -> Result<()> {
73 self.ops
74 .delete_server(
75 self.client.subscription_id(),
76 resource_group_name,
77 server_name,
78 )
79 .await
80 }
81
82 pub async fn list_databases(
86 &self,
87 resource_group_name: &str,
88 server_name: &str,
89 ) -> Result<DatabaseListResult> {
90 self.ops
91 .list_databases(
92 self.client.subscription_id(),
93 resource_group_name,
94 server_name,
95 )
96 .await
97 }
98
99 pub async fn get_database(
101 &self,
102 resource_group_name: &str,
103 server_name: &str,
104 database_name: &str,
105 ) -> Result<Database> {
106 self.ops
107 .get_database(
108 self.client.subscription_id(),
109 resource_group_name,
110 server_name,
111 database_name,
112 )
113 .await
114 }
115
116 pub async fn create_database(
118 &self,
119 resource_group_name: &str,
120 server_name: &str,
121 database_name: &str,
122 body: &DatabaseCreateRequest,
123 ) -> Result<Database> {
124 self.ops
125 .create_database(
126 self.client.subscription_id(),
127 resource_group_name,
128 server_name,
129 database_name,
130 body,
131 )
132 .await
133 }
134
135 pub async fn delete_database(
137 &self,
138 resource_group_name: &str,
139 server_name: &str,
140 database_name: &str,
141 ) -> Result<()> {
142 self.ops
143 .delete_database(
144 self.client.subscription_id(),
145 resource_group_name,
146 server_name,
147 database_name,
148 )
149 .await
150 }
151
152 pub async fn get_server_audit_policy(
156 &self,
157 resource_group_name: &str,
158 server_name: &str,
159 ) -> Result<ServerBlobAuditingPolicy> {
160 self.ops
161 .get_server_audit_policy(
162 self.client.subscription_id(),
163 resource_group_name,
164 server_name,
165 )
166 .await
167 }
168
169 pub async fn enable_server_auditing(
171 &self,
172 resource_group_name: &str,
173 server_name: &str,
174 body: &EnableServerAuditingRequest,
175 ) -> Result<ServerBlobAuditingPolicy> {
176 self.ops
177 .enable_server_auditing(
178 self.client.subscription_id(),
179 resource_group_name,
180 server_name,
181 body,
182 )
183 .await
184 }
185
186 pub async fn list_firewall_rules(
190 &self,
191 resource_group_name: &str,
192 server_name: &str,
193 ) -> Result<FirewallRuleListResult> {
194 self.ops
195 .list_firewall_rules(
196 self.client.subscription_id(),
197 resource_group_name,
198 server_name,
199 )
200 .await
201 }
202
203 pub async fn create_firewall_rule(
205 &self,
206 resource_group_name: &str,
207 server_name: &str,
208 firewall_rule_name: &str,
209 body: &FirewallRuleCreateRequest,
210 ) -> Result<FirewallRule> {
211 self.ops
212 .create_firewall_rule(
213 self.client.subscription_id(),
214 resource_group_name,
215 server_name,
216 firewall_rule_name,
217 body,
218 )
219 .await
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::{
227 MockClient,
228 types::sql::{DatabaseSku, FirewallRuleProperties, ServerCreateOrUpdateProperties},
229 };
230
231 const SUB_ID: &str = "test-subscription-id";
232 const RG: &str = "test-rg";
233 const SERVER: &str = "cloud-lite-test-sql-srv";
234 const DB: &str = "cloud-lite-test-db";
235 const FW_RULE: &str = "cloud-lite-test-fw-rule";
236
237 fn make_client(mock: MockClient) -> AzureHttpClient {
238 AzureHttpClient::from_mock(mock)
239 }
240
241 fn server_json() -> serde_json::Value {
242 serde_json::json!({
243 "id": format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}"),
244 "name": SERVER,
245 "type": "Microsoft.Sql/servers",
246 "location": "eastus",
247 "properties": {
248 "administratorLogin": "cloudliteadmin",
249 "fullyQualifiedDomainName": format!("{SERVER}.database.windows.net"),
250 "state": "Ready",
251 "version": "12.0"
252 }
253 })
254 }
255
256 fn database_json() -> serde_json::Value {
257 serde_json::json!({
258 "id": format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}/databases/{DB}"),
259 "name": DB,
260 "type": "Microsoft.Sql/servers/databases",
261 "location": "eastus",
262 "sku": { "name": "Basic", "tier": "Basic" },
263 "properties": {
264 "status": "Online",
265 "collation": "SQL_Latin1_General_CP1_CI_AS",
266 "requestedServiceObjectiveName": "Basic",
267 "currentServiceObjectiveName": "Basic"
268 }
269 })
270 }
271
272 fn firewall_rule_json() -> serde_json::Value {
273 serde_json::json!({
274 "id": format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}/firewallRules/{FW_RULE}"),
275 "name": FW_RULE,
276 "type": "Microsoft.Sql/servers/firewallRules",
277 "properties": {
278 "startIpAddress": "0.0.0.0",
279 "endIpAddress": "0.0.0.0"
280 }
281 })
282 }
283
284 #[tokio::test]
285 async fn list_servers_returns_list() {
286 let mut mock = MockClient::new();
287 mock.expect_get(&format!(
288 "/subscriptions/{SUB_ID}/providers/Microsoft.Sql/servers"
289 ))
290 .returning_json(serde_json::json!({ "value": [server_json()] }));
291 let client = make_client(mock);
292 let result = client
293 .sql()
294 .list_servers()
295 .await
296 .expect("list_servers failed");
297 assert_eq!(result.value.len(), 1);
298 let s = &result.value[0];
299 assert_eq!(s.name.as_deref(), Some(SERVER));
300 let props = s.properties.as_ref().unwrap();
301 assert_eq!(props.state.as_deref(), Some("Ready"));
302 }
303
304 #[tokio::test]
305 async fn get_server_deserializes_properties() {
306 let mut mock = MockClient::new();
307 mock.expect_get(&format!(
308 "/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}"
309 ))
310 .returning_json(server_json());
311 let client = make_client(mock);
312 let s = client
313 .sql()
314 .get_server(RG, SERVER)
315 .await
316 .expect("get_server failed");
317 assert_eq!(s.name.as_deref(), Some(SERVER));
318 let props = s.properties.as_ref().unwrap();
319 assert_eq!(props.administrator_login.as_deref(), Some("cloudliteadmin"));
320 assert_eq!(
321 props.fully_qualified_domain_name.as_deref(),
322 Some(&format!("{SERVER}.database.windows.net") as &str)
323 );
324 assert_eq!(props.version.as_deref(), Some("12.0"));
325 }
326
327 #[tokio::test]
328 async fn create_server_sends_body() {
329 let mut mock = MockClient::new();
330 mock.expect_put(&format!(
331 "/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}"
332 ))
333 .returning_json(server_json());
334 let client = make_client(mock);
335 let body = ServerCreateRequest {
336 location: "eastus".into(),
337 properties: Some(ServerCreateOrUpdateProperties {
338 administrator_login: "cloudliteadmin".into(),
339 administrator_login_password: Some("Password123!".into()),
340 version: Some("12.0".into()),
341 }),
342 ..Default::default()
343 };
344 let s = client
345 .sql()
346 .create_server(RG, SERVER, &body)
347 .await
348 .expect("create_server failed");
349 assert_eq!(s.name.as_deref(), Some(SERVER));
350 }
351
352 #[tokio::test]
353 async fn delete_server_succeeds() {
354 let mut mock = MockClient::new();
355 mock.expect_delete(&format!(
356 "/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}"
357 ))
358 .returning_json(serde_json::json!({}));
359 let client = make_client(mock);
360 client
361 .sql()
362 .delete_server(RG, SERVER)
363 .await
364 .expect("delete_server failed");
365 }
366
367 #[tokio::test]
368 async fn list_databases_returns_list() {
369 let mut mock = MockClient::new();
370 mock.expect_get(
371 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}/databases"),
372 )
373 .returning_json(serde_json::json!({ "value": [database_json()] }));
374 let client = make_client(mock);
375 let result = client
376 .sql()
377 .list_databases(RG, SERVER)
378 .await
379 .expect("list_databases failed");
380 assert_eq!(result.value.len(), 1);
381 let db = &result.value[0];
382 let props = db.properties.as_ref().unwrap();
383 assert_eq!(props.status.as_deref(), Some("Online"));
384 }
385
386 #[tokio::test]
387 async fn get_database_deserializes_properties() {
388 let mut mock = MockClient::new();
389 mock.expect_get(
390 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}/databases/{DB}"),
391 )
392 .returning_json(database_json());
393 let client = make_client(mock);
394 let db = client
395 .sql()
396 .get_database(RG, SERVER, DB)
397 .await
398 .expect("get_database failed");
399 assert_eq!(db.name.as_deref(), Some(DB));
400 let props = db.properties.as_ref().unwrap();
401 assert_eq!(
402 props.current_service_objective_name.as_deref(),
403 Some("Basic")
404 );
405 assert_eq!(
406 props.collation.as_deref(),
407 Some("SQL_Latin1_General_CP1_CI_AS")
408 );
409 let sku = db.sku.as_ref().unwrap();
410 assert_eq!(sku.name, "Basic");
411 }
412
413 #[tokio::test]
414 async fn create_database_sends_body() {
415 let mut mock = MockClient::new();
416 mock.expect_put(
417 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}/databases/{DB}"),
418 )
419 .returning_json(database_json());
420 let client = make_client(mock);
421 let body = DatabaseCreateRequest {
422 location: "eastus".into(),
423 sku: Some(DatabaseSku {
424 name: "Basic".into(),
425 ..Default::default()
426 }),
427 ..Default::default()
428 };
429 let db = client
430 .sql()
431 .create_database(RG, SERVER, DB, &body)
432 .await
433 .expect("create_database failed");
434 assert_eq!(db.name.as_deref(), Some(DB));
435 }
436
437 #[tokio::test]
438 async fn delete_database_succeeds() {
439 let mut mock = MockClient::new();
440 mock.expect_delete(
441 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}/databases/{DB}"),
442 )
443 .returning_json(serde_json::json!({}));
444 let client = make_client(mock);
445 client
446 .sql()
447 .delete_database(RG, SERVER, DB)
448 .await
449 .expect("delete_database failed");
450 }
451
452 #[tokio::test]
453 async fn list_firewall_rules_returns_list() {
454 let mut mock = MockClient::new();
455 mock.expect_get(
456 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}/firewallRules"),
457 )
458 .returning_json(serde_json::json!({ "value": [firewall_rule_json()] }));
459 let client = make_client(mock);
460 let result = client
461 .sql()
462 .list_firewall_rules(RG, SERVER)
463 .await
464 .expect("list_firewall_rules failed");
465 assert_eq!(result.value.len(), 1);
466 assert_eq!(result.value[0].name.as_deref(), Some(FW_RULE));
467 }
468
469 #[tokio::test]
470 async fn create_firewall_rule_sends_body() {
471 let mut mock = MockClient::new();
472 mock.expect_put(
473 &format!("/subscriptions/{SUB_ID}/resourceGroups/{RG}/providers/Microsoft.Sql/servers/{SERVER}/firewallRules/{FW_RULE}"),
474 )
475 .returning_json(firewall_rule_json());
476 let client = make_client(mock);
477 let body = FirewallRuleCreateRequest {
478 properties: Some(FirewallRuleProperties {
479 start_ip_address: "0.0.0.0".into(),
480 end_ip_address: "0.0.0.0".into(),
481 }),
482 };
483 let fw = client
484 .sql()
485 .create_firewall_rule(RG, SERVER, FW_RULE, &body)
486 .await
487 .expect("create_firewall_rule failed");
488 assert_eq!(fw.name.as_deref(), Some(FW_RULE));
489 let props = fw.properties.as_ref().unwrap();
490 assert_eq!(props.start_ip_address, "0.0.0.0");
491 }
492}