1use crate::{
8 AwsHttpClient, Result,
9 ops::ec2::Ec2Ops,
10 types::ec2::{
11 AssociateIamInstanceProfileRequest, AssociateIamInstanceProfileResponse,
12 AuthorizeSecurityGroupIngressRequest, AuthorizeSecurityGroupIngressResponse,
13 CreateFlowLogsRequest, CreateFlowLogsResponse, CreateSnapshotRequest, CreateTagsRequest,
14 DeleteNatGatewayRequest, DeleteNatGatewayResponse, DeleteSecurityGroupRequest,
15 DeleteSecurityGroupResponse, DeleteSnapshotRequest, DeleteVolumeRequest,
16 DeleteVpcEndpointsRequest, DeleteVpcEndpointsResponse, DeregisterImageRequest,
17 DeregisterImageResponse, DescribeAddressesRequest, DescribeAddressesResponse,
18 DescribeFlowLogsRequest, DescribeFlowLogsResponse, DescribeImagesRequest,
19 DescribeImagesResponse, DescribeInstancesRequest, DescribeInstancesResponse,
20 DescribeLaunchTemplateVersionsRequest, DescribeLaunchTemplateVersionsResponse,
21 DescribeLaunchTemplatesRequest, DescribeLaunchTemplatesResponse,
22 DescribeNatGatewaysRequest, DescribeNatGatewaysResponse, DescribeNetworkAclsRequest,
23 DescribeNetworkAclsResponse, DescribeRouteTablesRequest, DescribeRouteTablesResponse,
24 DescribeSecurityGroupsRequest, DescribeSecurityGroupsResponse,
25 DescribeSnapshotAttributeRequest, DescribeSnapshotAttributeResponse,
26 DescribeSnapshotsRequest, DescribeSnapshotsResponse, DescribeVolumesRequest,
27 DescribeVolumesResponse, DescribeVpcEndpointsRequest, DescribeVpcEndpointsResponse,
28 DescribeVpcPeeringConnectionsRequest, DescribeVpcPeeringConnectionsResponse,
29 DescribeVpcsRequest, DescribeVpcsResponse, DetachVolumeRequest,
30 EnableEbsEncryptionByDefaultRequest, EnableEbsEncryptionByDefaultResponse,
31 EnableImageBlockPublicAccessRequest, EnableImageBlockPublicAccessResponse,
32 EnableSnapshotBlockPublicAccessRequest, EnableSnapshotBlockPublicAccessResponse,
33 GetEbsEncryptionByDefaultRequest, GetEbsEncryptionByDefaultResponse,
34 ModifyImageAttributeRequest, ModifyInstanceAttributeRequest,
35 ModifyInstanceMetadataOptionsRequest, ModifyInstanceMetadataOptionsResponse,
36 ModifySnapshotAttributeRequest, ModifyVolumeRequest, ModifyVolumeResponse,
37 MonitorInstancesRequest, MonitorInstancesResponse, ReleaseAddressRequest,
38 RevokeSecurityGroupEgressRequest, RevokeSecurityGroupEgressResponse,
39 RevokeSecurityGroupIngressRequest, RevokeSecurityGroupIngressResponse, Snapshot,
40 StartInstancesRequest, StartInstancesResponse, StopInstancesRequest, StopInstancesResponse,
41 TerminateInstancesRequest, TerminateInstancesResponse, VolumeAttachment,
42 VpcPeeringConnection,
43 },
44};
45
46pub struct Ec2Client<'a> {
48 ops: Ec2Ops<'a>,
49}
50
51impl<'a> Ec2Client<'a> {
52 pub(crate) fn new(client: &'a AwsHttpClient) -> Self {
54 Self {
55 ops: Ec2Ops::new(client),
56 }
57 }
58
59 pub async fn describe_instances(
61 &self,
62 body: &DescribeInstancesRequest,
63 ) -> Result<DescribeInstancesResponse> {
64 self.ops.describe_instances(body).await
65 }
66
67 pub async fn describe_volumes(
69 &self,
70 body: &DescribeVolumesRequest,
71 ) -> Result<DescribeVolumesResponse> {
72 self.ops.describe_volumes(body).await
73 }
74
75 pub async fn describe_snapshots(
77 &self,
78 body: &DescribeSnapshotsRequest,
79 ) -> Result<DescribeSnapshotsResponse> {
80 self.ops.describe_snapshots(body).await
81 }
82
83 pub async fn describe_images(
85 &self,
86 body: &DescribeImagesRequest,
87 ) -> Result<DescribeImagesResponse> {
88 self.ops.describe_images(body).await
89 }
90
91 pub async fn describe_security_groups(
93 &self,
94 body: &DescribeSecurityGroupsRequest,
95 ) -> Result<DescribeSecurityGroupsResponse> {
96 self.ops.describe_security_groups(body).await
97 }
98
99 pub async fn describe_addresses(
101 &self,
102 body: &DescribeAddressesRequest,
103 ) -> Result<DescribeAddressesResponse> {
104 self.ops.describe_addresses(body).await
105 }
106
107 pub async fn describe_nat_gateways(
109 &self,
110 body: &DescribeNatGatewaysRequest,
111 ) -> Result<DescribeNatGatewaysResponse> {
112 self.ops.describe_nat_gateways(body).await
113 }
114
115 pub async fn describe_route_tables(
117 &self,
118 body: &DescribeRouteTablesRequest,
119 ) -> Result<DescribeRouteTablesResponse> {
120 self.ops.describe_route_tables(body).await
121 }
122
123 pub async fn describe_network_acls(
125 &self,
126 body: &DescribeNetworkAclsRequest,
127 ) -> Result<DescribeNetworkAclsResponse> {
128 self.ops.describe_network_acls(body).await
129 }
130
131 pub async fn describe_flow_logs(
133 &self,
134 body: &DescribeFlowLogsRequest,
135 ) -> Result<DescribeFlowLogsResponse> {
136 self.ops.describe_flow_logs(body).await
137 }
138
139 pub async fn describe_vpcs(&self, body: &DescribeVpcsRequest) -> Result<DescribeVpcsResponse> {
141 self.ops.describe_vpcs(body).await
142 }
143
144 pub async fn describe_vpc_endpoints(
146 &self,
147 body: &DescribeVpcEndpointsRequest,
148 ) -> Result<DescribeVpcEndpointsResponse> {
149 self.ops.describe_vpc_endpoints(body).await
150 }
151
152 pub async fn describe_vpc_peering_connections(
163 &self,
164 body: &DescribeVpcPeeringConnectionsRequest,
165 ) -> Result<DescribeVpcPeeringConnectionsResponse> {
166 self.ops.describe_vpc_peering_connections(body).await
167 }
168
169 pub async fn list_vpc_peering_connections(&self) -> Result<Vec<VpcPeeringConnection>> {
171 let body = DescribeVpcPeeringConnectionsRequest {
172 ..Default::default()
173 };
174 let resp = self.ops.describe_vpc_peering_connections(&body).await?;
175 Ok(resp.vpc_peering_connections)
176 }
177
178 pub async fn describe_launch_templates(
180 &self,
181 body: &DescribeLaunchTemplatesRequest,
182 ) -> Result<DescribeLaunchTemplatesResponse> {
183 self.ops.describe_launch_templates(body).await
184 }
185
186 pub async fn describe_launch_template_versions(
188 &self,
189 body: &DescribeLaunchTemplateVersionsRequest,
190 ) -> Result<DescribeLaunchTemplateVersionsResponse> {
191 self.ops.describe_launch_template_versions(body).await
192 }
193
194 pub async fn describe_snapshot_attribute(
196 &self,
197 body: &DescribeSnapshotAttributeRequest,
198 ) -> Result<DescribeSnapshotAttributeResponse> {
199 self.ops.describe_snapshot_attribute(body).await
200 }
201
202 pub async fn get_ebs_encryption_by_default(
204 &self,
205 body: &GetEbsEncryptionByDefaultRequest,
206 ) -> Result<GetEbsEncryptionByDefaultResponse> {
207 self.ops.get_ebs_encryption_by_default(body).await
208 }
209
210 pub async fn terminate_instances(
212 &self,
213 body: &TerminateInstancesRequest,
214 ) -> Result<TerminateInstancesResponse> {
215 self.ops.terminate_instances(body).await
216 }
217
218 pub async fn stop_instances(
220 &self,
221 body: &StopInstancesRequest,
222 ) -> Result<StopInstancesResponse> {
223 self.ops.stop_instances(body).await
224 }
225
226 pub async fn start_instances(
228 &self,
229 body: &StartInstancesRequest,
230 ) -> Result<StartInstancesResponse> {
231 self.ops.start_instances(body).await
232 }
233
234 pub async fn modify_instance_attribute(
236 &self,
237 body: &ModifyInstanceAttributeRequest,
238 ) -> Result<()> {
239 self.ops.modify_instance_attribute(body).await
240 }
241
242 pub async fn modify_instance_metadata_options(
244 &self,
245 body: &ModifyInstanceMetadataOptionsRequest,
246 ) -> Result<ModifyInstanceMetadataOptionsResponse> {
247 self.ops.modify_instance_metadata_options(body).await
248 }
249
250 pub async fn monitor_instances(
252 &self,
253 body: &MonitorInstancesRequest,
254 ) -> Result<MonitorInstancesResponse> {
255 self.ops.monitor_instances(body).await
256 }
257
258 pub async fn associate_iam_instance_profile(
260 &self,
261 body: &AssociateIamInstanceProfileRequest,
262 ) -> Result<AssociateIamInstanceProfileResponse> {
263 self.ops.associate_iam_instance_profile(body).await
264 }
265
266 pub async fn detach_volume(&self, body: &DetachVolumeRequest) -> Result<VolumeAttachment> {
268 self.ops.detach_volume(body).await
269 }
270
271 pub async fn delete_volume(&self, body: &DeleteVolumeRequest) -> Result<()> {
273 self.ops.delete_volume(body).await
274 }
275
276 pub async fn modify_volume(&self, body: &ModifyVolumeRequest) -> Result<ModifyVolumeResponse> {
278 self.ops.modify_volume(body).await
279 }
280
281 pub async fn create_snapshot(&self, body: &CreateSnapshotRequest) -> Result<Snapshot> {
283 self.ops.create_snapshot(body).await
284 }
285
286 pub async fn delete_snapshot(&self, body: &DeleteSnapshotRequest) -> Result<()> {
288 self.ops.delete_snapshot(body).await
289 }
290
291 pub async fn modify_snapshot_attribute(
293 &self,
294 body: &ModifySnapshotAttributeRequest,
295 ) -> Result<()> {
296 self.ops.modify_snapshot_attribute(body).await
297 }
298
299 pub async fn enable_snapshot_block_public_access(
301 &self,
302 body: &EnableSnapshotBlockPublicAccessRequest,
303 ) -> Result<EnableSnapshotBlockPublicAccessResponse> {
304 self.ops.enable_snapshot_block_public_access(body).await
305 }
306
307 pub async fn deregister_image(
309 &self,
310 body: &DeregisterImageRequest,
311 ) -> Result<DeregisterImageResponse> {
312 self.ops.deregister_image(body).await
313 }
314
315 pub async fn modify_image_attribute(&self, body: &ModifyImageAttributeRequest) -> Result<()> {
317 self.ops.modify_image_attribute(body).await
318 }
319
320 pub async fn enable_image_block_public_access(
322 &self,
323 body: &EnableImageBlockPublicAccessRequest,
324 ) -> Result<EnableImageBlockPublicAccessResponse> {
325 self.ops.enable_image_block_public_access(body).await
326 }
327
328 pub async fn revoke_security_group_ingress(
330 &self,
331 body: &RevokeSecurityGroupIngressRequest,
332 ) -> Result<RevokeSecurityGroupIngressResponse> {
333 self.ops.revoke_security_group_ingress(body).await
334 }
335
336 pub async fn revoke_security_group_egress(
338 &self,
339 body: &RevokeSecurityGroupEgressRequest,
340 ) -> Result<RevokeSecurityGroupEgressResponse> {
341 self.ops.revoke_security_group_egress(body).await
342 }
343
344 pub async fn authorize_security_group_ingress(
346 &self,
347 body: &AuthorizeSecurityGroupIngressRequest,
348 ) -> Result<AuthorizeSecurityGroupIngressResponse> {
349 self.ops.authorize_security_group_ingress(body).await
350 }
351
352 pub async fn delete_security_group(
354 &self,
355 body: &DeleteSecurityGroupRequest,
356 ) -> Result<DeleteSecurityGroupResponse> {
357 self.ops.delete_security_group(body).await
358 }
359
360 pub async fn release_address(&self, body: &ReleaseAddressRequest) -> Result<()> {
362 self.ops.release_address(body).await
363 }
364
365 pub async fn delete_nat_gateway(
367 &self,
368 body: &DeleteNatGatewayRequest,
369 ) -> Result<DeleteNatGatewayResponse> {
370 self.ops.delete_nat_gateway(body).await
371 }
372
373 pub async fn delete_vpc_endpoints(
375 &self,
376 body: &DeleteVpcEndpointsRequest,
377 ) -> Result<DeleteVpcEndpointsResponse> {
378 self.ops.delete_vpc_endpoints(body).await
379 }
380
381 pub async fn create_flow_logs(
383 &self,
384 body: &CreateFlowLogsRequest,
385 ) -> Result<CreateFlowLogsResponse> {
386 self.ops.create_flow_logs(body).await
387 }
388
389 pub async fn create_tags(&self, body: &CreateTagsRequest) -> Result<()> {
391 self.ops.create_tags(body).await
392 }
393
394 pub async fn enable_ebs_encryption_by_default(
396 &self,
397 body: &EnableEbsEncryptionByDefaultRequest,
398 ) -> Result<EnableEbsEncryptionByDefaultResponse> {
399 self.ops.enable_ebs_encryption_by_default(body).await
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use crate::AwsHttpClient;
406 use crate::mock_client::MockClient;
407 use crate::test_support::ec2_mock_helpers::Ec2MockHelpers;
408
409 fn ec2_response(action: &str, inner: &str) -> Vec<u8> {
412 format!(
413 "<{action}Response>\
414 <requestId>test-request-id</requestId>\
415 {inner}\
416 </{action}Response>"
417 )
418 .into_bytes()
419 }
420
421 #[tokio::test]
422 async fn describe_instances_returns_parsed_reservation() {
423 let mut mock = MockClient::new();
424 mock.expect_describe_instances()
425 .returning_bytes(ec2_response(
426 "DescribeInstances",
427 "<reservationSet>\
428 <item>\
429 <reservationId>r-0123456789abcdef0</reservationId>\
430 </item>\
431 </reservationSet>",
432 ));
433
434 let client = AwsHttpClient::from_mock(mock);
435 let body = crate::types::ec2::DescribeInstancesRequest::default();
436 let response = client.ec2().describe_instances(&body).await.unwrap();
437
438 assert_eq!(response.reservations.len(), 1);
439 assert_eq!(
440 response.reservations[0].reservation_id.as_deref(),
441 Some("r-0123456789abcdef0")
442 );
443 }
444
445 #[tokio::test]
446 async fn describe_instances_handles_empty_response() {
447 let mut mock = MockClient::new();
448 mock.expect_describe_instances()
449 .returning_bytes(ec2_response("DescribeInstances", ""));
450
451 let client = AwsHttpClient::from_mock(mock);
452 let body = crate::types::ec2::DescribeInstancesRequest::default();
453 let response = client.ec2().describe_instances(&body).await.unwrap();
454 assert!(response.reservations.is_empty());
455 }
456
457 #[tokio::test]
458 async fn describe_launch_templates_returns_parsed_templates() {
459 let mut mock = MockClient::new();
460 mock.expect_describe_launch_templates()
461 .returning_bytes(ec2_response(
462 "DescribeLaunchTemplates",
463 "<launchTemplates>\
464 <item>\
465 <launchTemplateId>lt-0123456789abcdef0</launchTemplateId>\
466 <launchTemplateName>my-template</launchTemplateName>\
467 </item>\
468 </launchTemplates>",
469 ));
470
471 let client = AwsHttpClient::from_mock(mock);
472 let body = crate::types::ec2::DescribeLaunchTemplatesRequest::default();
473 let response = client.ec2().describe_launch_templates(&body).await.unwrap();
474
475 assert_eq!(response.launch_templates.len(), 1);
476 let lt = &response.launch_templates[0];
477 assert_eq!(
478 lt.launch_template_id.as_deref(),
479 Some("lt-0123456789abcdef0")
480 );
481 assert_eq!(lt.launch_template_name.as_deref(), Some("my-template"));
482 }
483
484 #[tokio::test]
485 async fn describe_launch_templates_handles_empty_response() {
486 let mut mock = MockClient::new();
487 mock.expect_describe_launch_templates()
488 .returning_bytes(ec2_response("DescribeLaunchTemplates", ""));
489
490 let client = AwsHttpClient::from_mock(mock);
491 let body = crate::types::ec2::DescribeLaunchTemplatesRequest::default();
492 let response = client.ec2().describe_launch_templates(&body).await.unwrap();
493 assert!(response.launch_templates.is_empty());
494 }
495
496 #[tokio::test]
497 async fn describe_launch_template_versions_returns_parsed_versions() {
498 let mut mock = MockClient::new();
499 mock.expect_describe_launch_template_versions()
500 .returning_bytes(ec2_response(
501 "DescribeLaunchTemplateVersions",
502 "<launchTemplateVersionSet>\
503 <item>\
504 <launchTemplateId>lt-0123456789abcdef0</launchTemplateId>\
505 <launchTemplateName>my-template</launchTemplateName>\
506 <versionNumber>1</versionNumber>\
507 <launchTemplateData>\
508 <imageId>ami-abcdef01</imageId>\
509 <instanceType>t3.micro</instanceType>\
510 <metadataOptions>\
511 <httpTokens>required</httpTokens>\
512 </metadataOptions>\
513 </launchTemplateData>\
514 </item>\
515 </launchTemplateVersionSet>",
516 ));
517
518 let client = AwsHttpClient::from_mock(mock);
519 let body = crate::types::ec2::DescribeLaunchTemplateVersionsRequest {
520 launch_template_id: Some("lt-0123456789abcdef0".to_string()),
521 ..Default::default()
522 };
523 let response = client
524 .ec2()
525 .describe_launch_template_versions(&body)
526 .await
527 .unwrap();
528
529 assert_eq!(response.launch_template_versions.len(), 1);
530 let ver = &response.launch_template_versions[0];
531 assert_eq!(
532 ver.launch_template_id.as_deref(),
533 Some("lt-0123456789abcdef0")
534 );
535 assert_eq!(ver.launch_template_name.as_deref(), Some("my-template"));
536 assert_eq!(ver.version_number, Some(1));
537
538 let data = ver.launch_template_data.as_ref().unwrap();
539 assert_eq!(data.image_id.as_deref(), Some("ami-abcdef01"));
540 assert_eq!(data.instance_type.as_deref(), Some("t3.micro"));
541 let metadata = data.metadata_options.as_ref().unwrap();
542 assert_eq!(metadata.http_tokens.as_deref(), Some("required"));
543 }
544
545 #[tokio::test]
546 async fn describe_launch_template_versions_handles_empty_response() {
547 let mut mock = MockClient::new();
548 mock.expect_describe_launch_template_versions()
549 .returning_bytes(ec2_response("DescribeLaunchTemplateVersions", ""));
550
551 let client = AwsHttpClient::from_mock(mock);
552 let body = crate::types::ec2::DescribeLaunchTemplateVersionsRequest::default();
553 let response = client
554 .ec2()
555 .describe_launch_template_versions(&body)
556 .await
557 .unwrap();
558 assert!(response.launch_template_versions.is_empty());
559 }
560
561 #[tokio::test]
566 async fn describe_volumes_returns_parsed_volumes() {
567 let mut mock = MockClient::new();
568 mock.expect_describe_volumes().returning_bytes(ec2_response(
569 "DescribeVolumes",
570 "<volumeSet>\
571 <item>\
572 <volumeId>vol-0123456789abcdef0</volumeId>\
573 <size>100</size>\
574 <volumeType>gp3</volumeType>\
575 <status>available</status>\
576 <encrypted>true</encrypted>\
577 <availabilityZone>eu-central-1a</availabilityZone>\
578 <tagSet>\
579 <item>\
580 <key>Name</key>\
581 <value>test-volume</value>\
582 </item>\
583 </tagSet>\
584 </item>\
585 </volumeSet>\
586 <nextToken>page-2-token</nextToken>",
587 ));
588
589 let client = AwsHttpClient::from_mock(mock);
590 let body = crate::types::ec2::DescribeVolumesRequest::default();
591 let response = client.ec2().describe_volumes(&body).await.unwrap();
592
593 assert_eq!(response.volumes.len(), 1);
594 let vol = &response.volumes[0];
595 assert_eq!(vol.volume_id.as_deref(), Some("vol-0123456789abcdef0"));
596 assert_eq!(vol.size, Some(100));
597 assert_eq!(vol.volume_type.as_deref(), Some("gp3"));
598 assert_eq!(vol.state.as_deref(), Some("available"));
599 assert_eq!(vol.encrypted, Some(true));
600 assert_eq!(vol.availability_zone.as_deref(), Some("eu-central-1a"));
601 assert_eq!(vol.tags.len(), 1);
602 assert_eq!(vol.tags[0].key.as_deref(), Some("Name"));
603 assert_eq!(vol.tags[0].value.as_deref(), Some("test-volume"));
604 assert_eq!(response.next_token.as_deref(), Some("page-2-token"));
605 }
606
607 #[tokio::test]
608 async fn describe_volumes_handles_empty_response() {
609 let mut mock = MockClient::new();
610 mock.expect_describe_volumes()
611 .returning_bytes(ec2_response("DescribeVolumes", ""));
612
613 let client = AwsHttpClient::from_mock(mock);
614 let body = crate::types::ec2::DescribeVolumesRequest::default();
615 let response = client.ec2().describe_volumes(&body).await.unwrap();
616 assert!(response.volumes.is_empty());
617 assert!(response.next_token.is_none());
618 }
619
620 #[tokio::test]
621 async fn modify_volume_returns_parsed_modification() {
622 let mut mock = MockClient::new();
623 mock.expect_modify_volume().returning_bytes(ec2_response(
624 "ModifyVolume",
625 "<volumeModification>\
626 <volumeId>vol-0123456789abcdef0</volumeId>\
627 <modificationState>modifying</modificationState>\
628 <targetIops>4000</targetIops>\
629 <targetVolumeType>gp3</targetVolumeType>\
630 <targetThroughput>250</targetThroughput>\
631 </volumeModification>",
632 ));
633
634 let client = AwsHttpClient::from_mock(mock);
635 let body = crate::types::ec2::ModifyVolumeRequest {
636 volume_id: "vol-0123456789abcdef0".into(),
637 iops: Some(4000),
638 ..Default::default()
639 };
640 let response = client.ec2().modify_volume(&body).await.unwrap();
641
642 let modification = response.volume_modification.unwrap();
643 assert_eq!(
644 modification.volume_id.as_deref(),
645 Some("vol-0123456789abcdef0")
646 );
647 assert_eq!(
648 modification.modification_state.as_deref(),
649 Some("modifying")
650 );
651 assert_eq!(modification.target_iops, Some(4000));
652 assert_eq!(modification.target_volume_type.as_deref(), Some("gp3"));
653 assert_eq!(modification.target_throughput, Some(250));
654 }
655
656 #[tokio::test]
657 async fn detach_volume_returns_parsed_attachment() {
658 let mut mock = MockClient::new();
659 mock.expect_detach_volume().returning_bytes(ec2_response(
660 "DetachVolume",
661 "<volumeId>vol-0123456789abcdef0</volumeId>\
662 <instanceId>i-0123456789abcdef0</instanceId>\
663 <device>/dev/sdf</device>\
664 <status>detaching</status>",
665 ));
666
667 let client = AwsHttpClient::from_mock(mock);
668 let body = crate::types::ec2::DetachVolumeRequest {
669 volume_id: "vol-0123456789abcdef0".into(),
670 instance_id: Some("i-0123456789abcdef0".into()),
671 };
672 let response = client.ec2().detach_volume(&body).await.unwrap();
673
674 assert_eq!(response.volume_id.as_deref(), Some("vol-0123456789abcdef0"));
675 assert_eq!(response.instance_id.as_deref(), Some("i-0123456789abcdef0"));
676 assert_eq!(response.device.as_deref(), Some("/dev/sdf"));
677 assert_eq!(response.state.as_deref(), Some("detaching"));
678 }
679
680 #[tokio::test]
681 async fn delete_volume_succeeds() {
682 let mut mock = MockClient::new();
683 mock.expect_delete_volume()
684 .returning_bytes(ec2_response("DeleteVolume", ""));
685
686 let client = AwsHttpClient::from_mock(mock);
687 let body = crate::types::ec2::DeleteVolumeRequest {
688 volume_id: "vol-0123456789abcdef0".into(),
689 };
690 let result = client.ec2().delete_volume(&body).await;
691 assert!(result.is_ok());
692 }
693
694 #[tokio::test]
700 async fn describe_snapshots_returns_parsed_snapshots() {
701 let mut mock = MockClient::new();
702 mock.expect_describe_snapshots()
703 .returning_bytes(ec2_response(
704 "DescribeSnapshots",
705 "<snapshotSet>\
706 <item>\
707 <snapshotId>snap-0123456789abcdef0</snapshotId>\
708 <volumeId>vol-0123456789abcdef0</volumeId>\
709 <volumeSize>100</volumeSize>\
710 <status>completed</status>\
711 <encrypted>false</encrypted>\
712 <startTime>2026-01-15T10:00:00.000Z</startTime>\
713 <tagSet>\
714 <item>\
715 <key>Name</key>\
716 <value>test-snapshot</value>\
717 </item>\
718 </tagSet>\
719 </item>\
720 </snapshotSet>\
721 <nextToken>page-2-token</nextToken>",
722 ));
723
724 let client = AwsHttpClient::from_mock(mock);
725 let body = crate::types::ec2::DescribeSnapshotsRequest::default();
726 let response = client.ec2().describe_snapshots(&body).await.unwrap();
727
728 assert_eq!(response.snapshots.len(), 1);
729 let snap = &response.snapshots[0];
730 assert_eq!(snap.snapshot_id.as_deref(), Some("snap-0123456789abcdef0"));
731 assert_eq!(snap.volume_id.as_deref(), Some("vol-0123456789abcdef0"));
732 assert_eq!(snap.volume_size, Some(100));
733 assert_eq!(snap.state.as_deref(), Some("completed"));
734 assert_eq!(snap.encrypted, Some(false));
735 assert_eq!(snap.tags.len(), 1);
736 assert_eq!(snap.tags[0].key.as_deref(), Some("Name"));
737 assert_eq!(response.next_token.as_deref(), Some("page-2-token"));
738 }
739
740 #[tokio::test]
741 async fn describe_snapshots_handles_empty_response() {
742 let mut mock = MockClient::new();
743 mock.expect_describe_snapshots()
744 .returning_bytes(ec2_response("DescribeSnapshots", ""));
745
746 let client = AwsHttpClient::from_mock(mock);
747 let body = crate::types::ec2::DescribeSnapshotsRequest::default();
748 let response = client.ec2().describe_snapshots(&body).await.unwrap();
749 assert!(response.snapshots.is_empty());
750 }
751
752 #[tokio::test]
753 async fn create_snapshot_returns_parsed_snapshot() {
754 let mut mock = MockClient::new();
755 mock.expect_create_snapshot().returning_bytes(ec2_response(
756 "CreateSnapshot",
757 "<snapshotId>snap-0123456789abcdef0</snapshotId>\
758 <volumeId>vol-0123456789abcdef0</volumeId>\
759 <volumeSize>50</volumeSize>\
760 <status>pending</status>\
761 <encrypted>true</encrypted>",
762 ));
763
764 let client = AwsHttpClient::from_mock(mock);
765 let body = crate::types::ec2::CreateSnapshotRequest {
766 volume_id: "vol-0123456789abcdef0".into(),
767 description: Some("test snapshot".into()),
768 };
769 let response = client.ec2().create_snapshot(&body).await.unwrap();
770
771 assert_eq!(
772 response.snapshot_id.as_deref(),
773 Some("snap-0123456789abcdef0")
774 );
775 assert_eq!(response.volume_id.as_deref(), Some("vol-0123456789abcdef0"));
776 assert_eq!(response.volume_size, Some(50));
777 assert_eq!(response.state.as_deref(), Some("pending"));
778 assert_eq!(response.encrypted, Some(true));
779 }
780
781 #[tokio::test]
782 async fn delete_snapshot_succeeds() {
783 let mut mock = MockClient::new();
784 mock.expect_delete_snapshot()
785 .returning_bytes(ec2_response("DeleteSnapshot", ""));
786
787 let client = AwsHttpClient::from_mock(mock);
788 let body = crate::types::ec2::DeleteSnapshotRequest {
789 snapshot_id: "snap-0123456789abcdef0".into(),
790 };
791 let result = client.ec2().delete_snapshot(&body).await;
792 assert!(result.is_ok());
793 }
794
795 #[tokio::test]
796 async fn describe_snapshot_attribute_returns_permissions() {
797 let mut mock = MockClient::new();
798 mock.expect_describe_snapshot_attribute()
799 .returning_bytes(ec2_response(
800 "DescribeSnapshotAttribute",
801 "<snapshotId>snap-0123456789abcdef0</snapshotId>\
802 <createVolumePermission>\
803 <item>\
804 <userId>123456789012</userId>\
805 </item>\
806 </createVolumePermission>",
807 ));
808
809 let client = AwsHttpClient::from_mock(mock);
810 let body = crate::types::ec2::DescribeSnapshotAttributeRequest {
811 snapshot_id: "snap-0123456789abcdef0".into(),
812 attribute: "createVolumePermission".into(),
813 };
814 let response = client
815 .ec2()
816 .describe_snapshot_attribute(&body)
817 .await
818 .unwrap();
819
820 assert_eq!(
821 response.snapshot_id.as_deref(),
822 Some("snap-0123456789abcdef0")
823 );
824 assert_eq!(response.create_volume_permissions.len(), 1);
825 assert_eq!(
826 response.create_volume_permissions[0].user_id.as_deref(),
827 Some("123456789012")
828 );
829 }
830
831 #[tokio::test]
832 async fn modify_snapshot_attribute_succeeds() {
833 let mut mock = MockClient::new();
834 mock.expect_modify_snapshot_attribute()
835 .returning_bytes(ec2_response("ModifySnapshotAttribute", ""));
836
837 let client = AwsHttpClient::from_mock(mock);
838 let body = crate::types::ec2::ModifySnapshotAttributeRequest {
839 snapshot_id: "snap-0123456789abcdef0".into(),
840 attribute: Some("createVolumePermission".into()),
841 ..Default::default()
842 };
843 let result = client.ec2().modify_snapshot_attribute(&body).await;
844 assert!(result.is_ok());
845 }
846
847 #[tokio::test]
852 async fn describe_images_returns_parsed_images() {
853 let mut mock = MockClient::new();
854 mock.expect_describe_images().returning_bytes(ec2_response(
855 "DescribeImages",
856 "<imagesSet>\
857 <item>\
858 <imageId>ami-0123456789abcdef0</imageId>\
859 <name>test-ami</name>\
860 <imageState>available</imageState>\
861 <isPublic>false</isPublic>\
862 <imageType>machine</imageType>\
863 <platformDetails>Linux/UNIX</platformDetails>\
864 <creationDate>2026-01-15T10:00:00.000Z</creationDate>\
865 <description>A test AMI</description>\
866 <blockDeviceMapping>\
867 <item>\
868 <deviceName>/dev/xvda</deviceName>\
869 </item>\
870 </blockDeviceMapping>\
871 <tagSet>\
872 <item>\
873 <key>Name</key>\
874 <value>test-image</value>\
875 </item>\
876 </tagSet>\
877 </item>\
878 </imagesSet>",
879 ));
880
881 let client = AwsHttpClient::from_mock(mock);
882 let body = crate::types::ec2::DescribeImagesRequest {
883 owners: vec!["self".into()],
884 ..Default::default()
885 };
886 let response = client.ec2().describe_images(&body).await.unwrap();
887
888 assert_eq!(response.images.len(), 1);
889 let img = &response.images[0];
890 assert_eq!(img.image_id.as_deref(), Some("ami-0123456789abcdef0"));
891 assert_eq!(img.name.as_deref(), Some("test-ami"));
892 assert_eq!(img.state.as_deref(), Some("available"));
893 assert_eq!(img.public, Some(false));
894 assert_eq!(img.image_type.as_deref(), Some("machine"));
895 assert_eq!(img.platform_details.as_deref(), Some("Linux/UNIX"));
896 assert_eq!(
897 img.creation_date.as_deref(),
898 Some("2026-01-15T10:00:00.000Z")
899 );
900 assert_eq!(img.description.as_deref(), Some("A test AMI"));
901 assert_eq!(img.block_device_mappings.len(), 1);
902 assert_eq!(
903 img.block_device_mappings[0].device_name.as_deref(),
904 Some("/dev/xvda")
905 );
906 assert_eq!(img.tags.len(), 1);
907 assert_eq!(img.tags[0].key.as_deref(), Some("Name"));
908 assert_eq!(img.tags[0].value.as_deref(), Some("test-image"));
909 }
910
911 #[tokio::test]
912 async fn describe_images_handles_empty_response() {
913 let mut mock = MockClient::new();
914 mock.expect_describe_images()
915 .returning_bytes(ec2_response("DescribeImages", ""));
916
917 let client = AwsHttpClient::from_mock(mock);
918 let body = crate::types::ec2::DescribeImagesRequest::default();
919 let response = client.ec2().describe_images(&body).await.unwrap();
920 assert!(response.images.is_empty());
921 }
922
923 #[tokio::test]
924 async fn deregister_image_returns_parsed_response() {
925 let mut mock = MockClient::new();
926 mock.expect_deregister_image().returning_bytes(ec2_response(
927 "DeregisterImage",
928 "<deleteSnapshotResultSet>\
929 <item>\
930 <snapshotId>snap-0123456789abcdef0</snapshotId>\
931 <return>true</return>\
932 </item>\
933 </deleteSnapshotResultSet>",
934 ));
935
936 let client = AwsHttpClient::from_mock(mock);
937 let body = crate::types::ec2::DeregisterImageRequest {
938 image_id: "ami-0123456789abcdef0".into(),
939 };
940 let response = client.ec2().deregister_image(&body).await.unwrap();
941
942 assert_eq!(response.delete_snapshot_results.len(), 1);
943 }
944
945 #[tokio::test]
946 async fn deregister_image_handles_empty_result() {
947 let mut mock = MockClient::new();
948 mock.expect_deregister_image()
949 .returning_bytes(ec2_response("DeregisterImage", ""));
950
951 let client = AwsHttpClient::from_mock(mock);
952 let body = crate::types::ec2::DeregisterImageRequest {
953 image_id: "ami-0123456789abcdef0".into(),
954 };
955 let response = client.ec2().deregister_image(&body).await.unwrap();
956 assert!(response.delete_snapshot_results.is_empty());
957 }
958
959 #[tokio::test]
960 async fn modify_image_attribute_succeeds() {
961 let mut mock = MockClient::new();
962 mock.expect_modify_image_attribute()
963 .returning_bytes(ec2_response("ModifyImageAttribute", ""));
964
965 let client = AwsHttpClient::from_mock(mock);
966 let body = crate::types::ec2::ModifyImageAttributeRequest {
967 image_id: "ami-0123456789abcdef0".into(),
968 ..Default::default()
969 };
970 let result = client.ec2().modify_image_attribute(&body).await;
971 assert!(result.is_ok());
972 }
973
974 #[tokio::test]
981 async fn describe_security_groups_returns_parsed_groups() {
982 let mut mock = MockClient::new();
983 mock.expect_describe_security_groups()
984 .returning_bytes(ec2_response(
985 "DescribeSecurityGroups",
986 "<securityGroupInfo>\
987 <item>\
988 <groupId>sg-0123456789abcdef0</groupId>\
989 <groupName>test-sg</groupName>\
990 <groupDescription>A test security group</groupDescription>\
991 <vpcId>vpc-abc123</vpcId>\
992 <ipPermissions>\
993 <item>\
994 <ipProtocol>tcp</ipProtocol>\
995 <fromPort>22</fromPort>\
996 <toPort>22</toPort>\
997 <ipRanges>\
998 <item>\
999 <cidrIp>10.0.0.0/8</cidrIp>\
1000 </item>\
1001 </ipRanges>\
1002 </item>\
1003 </ipPermissions>\
1004 <ipPermissionsEgress>\
1005 <item>\
1006 <ipProtocol>-1</ipProtocol>\
1007 <ipRanges>\
1008 <item>\
1009 <cidrIp>0.0.0.0/0</cidrIp>\
1010 </item>\
1011 </ipRanges>\
1012 </item>\
1013 </ipPermissionsEgress>\
1014 <tagSet>\
1015 <item>\
1016 <key>Name</key>\
1017 <value>my-sg</value>\
1018 </item>\
1019 </tagSet>\
1020 </item>\
1021 </securityGroupInfo>",
1022 ));
1023
1024 let client = AwsHttpClient::from_mock(mock);
1025 let body = crate::types::ec2::DescribeSecurityGroupsRequest::default();
1026 let response = client.ec2().describe_security_groups(&body).await.unwrap();
1027
1028 assert_eq!(response.security_groups.len(), 1);
1029 let sg = &response.security_groups[0];
1030 assert_eq!(sg.group_id.as_deref(), Some("sg-0123456789abcdef0"));
1031 assert_eq!(sg.group_name.as_deref(), Some("test-sg"));
1032 assert_eq!(sg.description.as_deref(), Some("A test security group"));
1033 assert_eq!(sg.vpc_id.as_deref(), Some("vpc-abc123"));
1034 assert_eq!(sg.ip_permissions.len(), 1);
1035 let perm = &sg.ip_permissions[0];
1036 assert_eq!(perm.ip_protocol.as_deref(), Some("tcp"));
1037 assert_eq!(perm.from_port, Some(22));
1038 assert_eq!(perm.to_port, Some(22));
1039 assert_eq!(perm.ip_ranges.len(), 1);
1040 assert_eq!(perm.ip_ranges[0].cidr_ip.as_deref(), Some("10.0.0.0/8"));
1041 assert_eq!(sg.ip_permissions_egress.len(), 1);
1042 assert_eq!(sg.tags.len(), 1);
1043 assert_eq!(sg.tags[0].key.as_deref(), Some("Name"));
1044 }
1045
1046 #[tokio::test]
1047 async fn describe_security_groups_handles_empty_response() {
1048 let mut mock = MockClient::new();
1049 mock.expect_describe_security_groups()
1050 .returning_bytes(ec2_response("DescribeSecurityGroups", ""));
1051
1052 let client = AwsHttpClient::from_mock(mock);
1053 let body = crate::types::ec2::DescribeSecurityGroupsRequest::default();
1054 let response = client.ec2().describe_security_groups(&body).await.unwrap();
1055 assert!(response.security_groups.is_empty());
1056 }
1057
1058 #[tokio::test]
1059 async fn authorize_security_group_ingress_returns_rules() {
1060 let mut mock = MockClient::new();
1061 mock.expect_authorize_security_group_ingress()
1062 .returning_bytes(ec2_response(
1063 "AuthorizeSecurityGroupIngress",
1064 "<securityGroupRuleSet>\
1065 <item>\
1066 <securityGroupRuleId>sgr-0123456789abcdef0</securityGroupRuleId>\
1067 <groupId>sg-0123456789abcdef0</groupId>\
1068 <ipProtocol>tcp</ipProtocol>\
1069 <fromPort>443</fromPort>\
1070 <toPort>443</toPort>\
1071 <cidrIpv4>0.0.0.0/0</cidrIpv4>\
1072 </item>\
1073 </securityGroupRuleSet>",
1074 ));
1075
1076 let client = AwsHttpClient::from_mock(mock);
1077 let body = crate::types::ec2::AuthorizeSecurityGroupIngressRequest {
1078 group_id: Some("sg-0123456789abcdef0".into()),
1079 ip_permissions: vec![crate::types::ec2::IpPermission {
1080 ip_protocol: Some("tcp".into()),
1081 from_port: Some(443),
1082 to_port: Some(443),
1083 ip_ranges: vec![crate::types::ec2::IpRange {
1084 cidr_ip: Some("0.0.0.0/0".into()),
1085 ..Default::default()
1086 }],
1087 ..Default::default()
1088 }],
1089 };
1090 let response = client
1091 .ec2()
1092 .authorize_security_group_ingress(&body)
1093 .await
1094 .unwrap();
1095
1096 assert_eq!(response.security_group_rules.len(), 1);
1097 let rule = &response.security_group_rules[0];
1098 assert_eq!(
1099 rule.security_group_rule_id.as_deref(),
1100 Some("sgr-0123456789abcdef0")
1101 );
1102 assert_eq!(rule.group_id.as_deref(), Some("sg-0123456789abcdef0"));
1103 assert_eq!(rule.ip_protocol.as_deref(), Some("tcp"));
1104 assert_eq!(rule.from_port, Some(443));
1105 assert_eq!(rule.to_port, Some(443));
1106 assert_eq!(rule.cidr_ipv4.as_deref(), Some("0.0.0.0/0"));
1107 }
1108
1109 #[tokio::test]
1110 async fn revoke_security_group_ingress_succeeds() {
1111 let mut mock = MockClient::new();
1112 mock.expect_revoke_security_group_ingress()
1113 .returning_bytes(ec2_response("RevokeSecurityGroupIngress", ""));
1114
1115 let client = AwsHttpClient::from_mock(mock);
1116 let body = crate::types::ec2::RevokeSecurityGroupIngressRequest {
1117 group_id: Some("sg-0123456789abcdef0".into()),
1118 ip_permissions: vec![crate::types::ec2::IpPermission {
1119 ip_protocol: Some("tcp".into()),
1120 from_port: Some(22),
1121 to_port: Some(22),
1122 ..Default::default()
1123 }],
1124 };
1125 let result = client.ec2().revoke_security_group_ingress(&body).await;
1126 assert!(result.is_ok());
1127 }
1128
1129 #[tokio::test]
1130 async fn revoke_security_group_egress_succeeds() {
1131 let mut mock = MockClient::new();
1132 mock.expect_revoke_security_group_egress()
1133 .returning_bytes(ec2_response("RevokeSecurityGroupEgress", ""));
1134
1135 let client = AwsHttpClient::from_mock(mock);
1136 let body = crate::types::ec2::RevokeSecurityGroupEgressRequest {
1137 group_id: "sg-0123456789abcdef0".into(),
1138 ip_permissions: vec![crate::types::ec2::IpPermission {
1139 ip_protocol: Some("-1".into()),
1140 ..Default::default()
1141 }],
1142 };
1143 let result = client.ec2().revoke_security_group_egress(&body).await;
1144 assert!(result.is_ok());
1145 }
1146
1147 #[tokio::test]
1148 async fn delete_security_group_succeeds() {
1149 let mut mock = MockClient::new();
1150 mock.expect_delete_security_group()
1151 .returning_bytes(ec2_response(
1152 "DeleteSecurityGroup",
1153 "<groupId>sg-0123456789abcdef0</groupId>",
1154 ));
1155
1156 let client = AwsHttpClient::from_mock(mock);
1157 let body = crate::types::ec2::DeleteSecurityGroupRequest {
1158 group_id: Some("sg-0123456789abcdef0".into()),
1159 };
1160 let response = client.ec2().delete_security_group(&body).await.unwrap();
1161 assert_eq!(response.group_id.as_deref(), Some("sg-0123456789abcdef0"));
1162 }
1163
1164 #[tokio::test]
1165 async fn describe_addresses_returns_parsed_addresses() {
1166 let mut mock = MockClient::new();
1167 mock.expect_describe_addresses()
1168 .returning_bytes(ec2_response(
1169 "DescribeAddresses",
1170 "<addressesSet>\
1171 <item>\
1172 <allocationId>eipalloc-0123456789abcdef0</allocationId>\
1173 <publicIp>203.0.113.25</publicIp>\
1174 <domain>vpc</domain>\
1175 </item>\
1176 </addressesSet>",
1177 ));
1178
1179 let client = AwsHttpClient::from_mock(mock);
1180 let body = crate::types::ec2::DescribeAddressesRequest::default();
1181 let response = client.ec2().describe_addresses(&body).await.unwrap();
1182
1183 assert_eq!(response.addresses.len(), 1);
1184 let addr = &response.addresses[0];
1185 assert_eq!(
1186 addr.allocation_id.as_deref(),
1187 Some("eipalloc-0123456789abcdef0")
1188 );
1189 assert_eq!(addr.public_ip.as_deref(), Some("203.0.113.25"));
1190 assert_eq!(addr.domain.as_deref(), Some("vpc"));
1191 }
1192
1193 #[tokio::test]
1194 async fn describe_addresses_handles_empty_response() {
1195 let mut mock = MockClient::new();
1196 mock.expect_describe_addresses()
1197 .returning_bytes(ec2_response("DescribeAddresses", ""));
1198
1199 let client = AwsHttpClient::from_mock(mock);
1200 let body = crate::types::ec2::DescribeAddressesRequest::default();
1201 let response = client.ec2().describe_addresses(&body).await.unwrap();
1202 assert!(response.addresses.is_empty());
1203 }
1204
1205 #[tokio::test]
1206 async fn release_address_succeeds() {
1207 let mut mock = MockClient::new();
1208 mock.expect_release_address()
1209 .returning_bytes(ec2_response("ReleaseAddress", ""));
1210
1211 let client = AwsHttpClient::from_mock(mock);
1212 let body = crate::types::ec2::ReleaseAddressRequest {
1213 allocation_id: Some("eipalloc-0123456789abcdef0".into()),
1214 };
1215 let result = client.ec2().release_address(&body).await;
1216 assert!(result.is_ok());
1217 }
1218
1219 #[tokio::test]
1220 async fn describe_nat_gateways_returns_parsed_gateways() {
1221 let mut mock = MockClient::new();
1222 mock.expect_describe_nat_gateways()
1223 .returning_bytes(ec2_response(
1224 "DescribeNatGateways",
1225 "<natGatewaySet>\
1226 <item>\
1227 <natGatewayId>nat-0123456789abcdef0</natGatewayId>\
1228 <state>available</state>\
1229 <vpcId>vpc-0abc123</vpcId>\
1230 <subnetId>subnet-0abc123</subnetId>\
1231 <natGatewayAddressSet>\
1232 <item>\
1233 <allocationId>eipalloc-0abc123</allocationId>\
1234 <publicIp>198.51.100.1</publicIp>\
1235 </item>\
1236 </natGatewayAddressSet>\
1237 </item>\
1238 </natGatewaySet>",
1239 ));
1240
1241 let client = AwsHttpClient::from_mock(mock);
1242 let body = crate::types::ec2::DescribeNatGatewaysRequest::default();
1243 let response = client.ec2().describe_nat_gateways(&body).await.unwrap();
1244
1245 assert_eq!(response.nat_gateways.len(), 1);
1246 let ngw = &response.nat_gateways[0];
1247 assert_eq!(ngw.nat_gateway_id.as_deref(), Some("nat-0123456789abcdef0"));
1248 assert_eq!(ngw.state.as_deref(), Some("available"));
1249 assert_eq!(ngw.vpc_id.as_deref(), Some("vpc-0abc123"));
1250 assert_eq!(ngw.nat_gateway_addresses.len(), 1);
1251 assert_eq!(
1252 ngw.nat_gateway_addresses[0].public_ip.as_deref(),
1253 Some("198.51.100.1")
1254 );
1255 }
1256
1257 #[tokio::test]
1258 async fn delete_nat_gateway_returns_id() {
1259 let mut mock = MockClient::new();
1260 mock.expect_delete_nat_gateway()
1261 .returning_bytes(ec2_response(
1262 "DeleteNatGateway",
1263 "<natGatewayId>nat-0123456789abcdef0</natGatewayId>",
1264 ));
1265
1266 let client = AwsHttpClient::from_mock(mock);
1267 let body = crate::types::ec2::DeleteNatGatewayRequest {
1268 nat_gateway_id: "nat-0123456789abcdef0".into(),
1269 };
1270 let response = client.ec2().delete_nat_gateway(&body).await.unwrap();
1271 assert_eq!(
1272 response.nat_gateway_id.as_deref(),
1273 Some("nat-0123456789abcdef0")
1274 );
1275 }
1276
1277 #[tokio::test]
1278 async fn describe_vpc_endpoints_returns_parsed_endpoints() {
1279 let mut mock = MockClient::new();
1280 mock.expect_describe_vpc_endpoints()
1281 .returning_bytes(ec2_response(
1282 "DescribeVpcEndpoints",
1283 "<vpcEndpointSet>\
1284 <item>\
1285 <vpcEndpointId>vpce-0123456789abcdef0</vpcEndpointId>\
1286 <vpcId>vpc-0abc123</vpcId>\
1287 <serviceName>com.amazonaws.eu-central-1.s3</serviceName>\
1288 <state>available</state>\
1289 </item>\
1290 </vpcEndpointSet>",
1291 ));
1292
1293 let client = AwsHttpClient::from_mock(mock);
1294 let body = crate::types::ec2::DescribeVpcEndpointsRequest::default();
1295 let response = client.ec2().describe_vpc_endpoints(&body).await.unwrap();
1296
1297 assert_eq!(response.vpc_endpoints.len(), 1);
1298 let vpce = &response.vpc_endpoints[0];
1299 assert_eq!(
1300 vpce.vpc_endpoint_id.as_deref(),
1301 Some("vpce-0123456789abcdef0")
1302 );
1303 assert_eq!(vpce.vpc_id.as_deref(), Some("vpc-0abc123"));
1304 assert_eq!(
1305 vpce.service_name.as_deref(),
1306 Some("com.amazonaws.eu-central-1.s3")
1307 );
1308 assert_eq!(vpce.state.as_deref(), Some("available"));
1309 }
1310
1311 #[tokio::test]
1312 async fn delete_vpc_endpoints_succeeds() {
1313 let mut mock = MockClient::new();
1314 mock.expect_delete_vpc_endpoints()
1315 .returning_bytes(ec2_response("DeleteVpcEndpoints", "<unsuccessful/>"));
1316
1317 let client = AwsHttpClient::from_mock(mock);
1318 let body = crate::types::ec2::DeleteVpcEndpointsRequest {
1319 vpc_endpoint_ids: vec!["vpce-0123456789abcdef0".into()],
1320 };
1321 let response = client.ec2().delete_vpc_endpoints(&body).await.unwrap();
1322 assert!(response.unsuccessful.is_empty());
1323 }
1324
1325 #[tokio::test]
1326 async fn describe_vpcs_returns_parsed_vpcs() {
1327 let mut mock = MockClient::new();
1328 mock.expect_describe_vpcs().returning_bytes(ec2_response(
1329 "DescribeVpcs",
1330 "<vpcSet>\
1331 <item>\
1332 <vpcId>vpc-0abc123</vpcId>\
1333 <cidrBlock>10.0.0.0/16</cidrBlock>\
1334 <state>available</state>\
1335 <isDefault>true</isDefault>\
1336 </item>\
1337 </vpcSet>",
1338 ));
1339
1340 let client = AwsHttpClient::from_mock(mock);
1341 let body = crate::types::ec2::DescribeVpcsRequest::default();
1342 let response = client.ec2().describe_vpcs(&body).await.unwrap();
1343
1344 assert_eq!(response.vpcs.len(), 1);
1345 let vpc = &response.vpcs[0];
1346 assert_eq!(vpc.vpc_id.as_deref(), Some("vpc-0abc123"));
1347 assert_eq!(vpc.cidr_block.as_deref(), Some("10.0.0.0/16"));
1348 assert_eq!(vpc.state.as_deref(), Some("available"));
1349 assert_eq!(vpc.is_default, Some(true));
1350 }
1351
1352 #[tokio::test]
1353 async fn describe_route_tables_returns_parsed_tables() {
1354 let mut mock = MockClient::new();
1355 mock.expect_describe_route_tables()
1356 .returning_bytes(ec2_response(
1357 "DescribeRouteTables",
1358 "<routeTableSet>\
1359 <item>\
1360 <routeTableId>rtb-0abc123</routeTableId>\
1361 <vpcId>vpc-0abc123</vpcId>\
1362 <routeSet>\
1363 <item>\
1364 <destinationCidrBlock>10.0.0.0/16</destinationCidrBlock>\
1365 <gatewayId>local</gatewayId>\
1366 <state>active</state>\
1367 </item>\
1368 </routeSet>\
1369 </item>\
1370 </routeTableSet>",
1371 ));
1372
1373 let client = AwsHttpClient::from_mock(mock);
1374 let body = crate::types::ec2::DescribeRouteTablesRequest::default();
1375 let response = client.ec2().describe_route_tables(&body).await.unwrap();
1376
1377 assert_eq!(response.route_tables.len(), 1);
1378 let rt = &response.route_tables[0];
1379 assert_eq!(rt.route_table_id.as_deref(), Some("rtb-0abc123"));
1380 assert_eq!(rt.vpc_id.as_deref(), Some("vpc-0abc123"));
1381 assert_eq!(rt.routes.len(), 1);
1382 assert_eq!(
1383 rt.routes[0].destination_cidr_block.as_deref(),
1384 Some("10.0.0.0/16")
1385 );
1386 assert_eq!(rt.routes[0].state.as_deref(), Some("active"));
1387 }
1388
1389 #[tokio::test]
1390 async fn describe_network_acls_returns_parsed_acls() {
1391 let mut mock = MockClient::new();
1392 mock.expect_describe_network_acls()
1393 .returning_bytes(ec2_response(
1394 "DescribeNetworkAcls",
1395 "<networkAclSet>\
1396 <item>\
1397 <networkAclId>acl-0abc123</networkAclId>\
1398 <vpcId>vpc-0abc123</vpcId>\
1399 <default>true</default>\
1400 <entrySet>\
1401 <item>\
1402 <ruleNumber>100</ruleNumber>\
1403 <protocol>-1</protocol>\
1404 <ruleAction>allow</ruleAction>\
1405 <egress>false</egress>\
1406 <cidrBlock>0.0.0.0/0</cidrBlock>\
1407 </item>\
1408 </entrySet>\
1409 </item>\
1410 </networkAclSet>",
1411 ));
1412
1413 let client = AwsHttpClient::from_mock(mock);
1414 let body = crate::types::ec2::DescribeNetworkAclsRequest::default();
1415 let response = client.ec2().describe_network_acls(&body).await.unwrap();
1416
1417 assert_eq!(response.network_acls.len(), 1);
1418 let acl = &response.network_acls[0];
1419 assert_eq!(acl.network_acl_id.as_deref(), Some("acl-0abc123"));
1420 assert_eq!(acl.vpc_id.as_deref(), Some("vpc-0abc123"));
1421 assert_eq!(acl.is_default, Some(true));
1422 assert_eq!(acl.entries.len(), 1);
1423 assert_eq!(acl.entries[0].rule_number, Some(100));
1424 assert_eq!(acl.entries[0].rule_action.as_deref(), Some("allow"));
1425 }
1426
1427 #[tokio::test]
1428 async fn describe_flow_logs_returns_parsed_logs() {
1429 let mut mock = MockClient::new();
1430 mock.expect_describe_flow_logs()
1431 .returning_bytes(ec2_response(
1432 "DescribeFlowLogs",
1433 "<flowLogSet>\
1434 <item>\
1435 <flowLogId>fl-0abc123</flowLogId>\
1436 <resourceId>vpc-0abc123</resourceId>\
1437 <trafficType>ALL</trafficType>\
1438 <logGroupName>/vpc/flow-logs</logGroupName>\
1439 <flowLogStatus>ACTIVE</flowLogStatus>\
1440 </item>\
1441 </flowLogSet>",
1442 ));
1443
1444 let client = AwsHttpClient::from_mock(mock);
1445 let body = crate::types::ec2::DescribeFlowLogsRequest::default();
1446 let response = client.ec2().describe_flow_logs(&body).await.unwrap();
1447
1448 assert_eq!(response.flow_logs.len(), 1);
1449 let fl = &response.flow_logs[0];
1450 assert_eq!(fl.flow_log_id.as_deref(), Some("fl-0abc123"));
1451 assert_eq!(fl.resource_id.as_deref(), Some("vpc-0abc123"));
1452 assert_eq!(fl.traffic_type.as_deref(), Some("ALL"));
1453 assert_eq!(fl.log_group_name.as_deref(), Some("/vpc/flow-logs"));
1454 assert_eq!(fl.flow_log_status.as_deref(), Some("ACTIVE"));
1455 }
1456
1457 #[tokio::test]
1458 async fn create_flow_logs_returns_ids() {
1459 let mut mock = MockClient::new();
1460 mock.expect_create_flow_logs().returning_bytes(ec2_response(
1461 "CreateFlowLogs",
1462 "<flowLogIdSet>\
1463 <item>fl-0abc123</item>\
1464 </flowLogIdSet>\
1465 <unsuccessful/>",
1466 ));
1467
1468 let client = AwsHttpClient::from_mock(mock);
1469 let body = crate::types::ec2::CreateFlowLogsRequest {
1470 resource_ids: vec!["vpc-0abc123".into()],
1471 resource_type: "VPC".into(),
1472 traffic_type: "ALL".into(),
1473 log_group_name: Some("/vpc/flow-logs".into()),
1474 deliver_logs_permission_arn: Some("arn:aws:iam::123456789012:role/flow-logs".into()),
1475 };
1476 let response = client.ec2().create_flow_logs(&body).await.unwrap();
1477 assert_eq!(response.flow_log_ids.len(), 1);
1478 assert_eq!(response.flow_log_ids[0], "fl-0abc123");
1479 assert!(response.unsuccessful.is_empty());
1480 }
1481
1482 #[tokio::test]
1483 async fn get_ebs_encryption_by_default_returns_state() {
1484 let mut mock = MockClient::new();
1485 mock.expect_get_ebs_encryption_by_default()
1486 .returning_bytes(ec2_response(
1487 "GetEbsEncryptionByDefault",
1488 "<ebsEncryptionByDefault>true</ebsEncryptionByDefault>",
1489 ));
1490
1491 let client = AwsHttpClient::from_mock(mock);
1492 let body = crate::types::ec2::GetEbsEncryptionByDefaultRequest::default();
1493 let response = client
1494 .ec2()
1495 .get_ebs_encryption_by_default(&body)
1496 .await
1497 .unwrap();
1498 assert_eq!(response.ebs_encryption_by_default, Some(true));
1499 }
1500
1501 #[tokio::test]
1502 async fn enable_ebs_encryption_by_default_succeeds() {
1503 let mut mock = MockClient::new();
1504 mock.expect_enable_ebs_encryption_by_default()
1505 .returning_bytes(ec2_response(
1506 "EnableEbsEncryptionByDefault",
1507 "<ebsEncryptionByDefault>true</ebsEncryptionByDefault>",
1508 ));
1509
1510 let client = AwsHttpClient::from_mock(mock);
1511 let body = crate::types::ec2::EnableEbsEncryptionByDefaultRequest::default();
1512 let response = client
1513 .ec2()
1514 .enable_ebs_encryption_by_default(&body)
1515 .await
1516 .unwrap();
1517 assert_eq!(response.ebs_encryption_by_default, Some(true));
1518 }
1519
1520 #[tokio::test]
1521 async fn terminate_instances_returns_state_changes() {
1522 let mut mock = MockClient::new();
1523 mock.expect_terminate_instances()
1524 .returning_bytes(ec2_response(
1525 "TerminateInstances",
1526 "<instancesSet>\
1527 <item>\
1528 <instanceId>i-0123456789abcdef0</instanceId>\
1529 <currentState><code>32</code><name>shutting-down</name></currentState>\
1530 <previousState><code>16</code><name>running</name></previousState>\
1531 </item>\
1532 </instancesSet>",
1533 ));
1534
1535 let client = AwsHttpClient::from_mock(mock);
1536 let body = crate::types::ec2::TerminateInstancesRequest {
1537 instance_ids: vec!["i-0123456789abcdef0".into()],
1538 };
1539 let response = client.ec2().terminate_instances(&body).await.unwrap();
1540
1541 assert_eq!(response.terminating_instances.len(), 1);
1542 let sc = &response.terminating_instances[0];
1543 assert_eq!(sc.instance_id.as_deref(), Some("i-0123456789abcdef0"));
1544 assert_eq!(
1545 sc.current_state.as_ref().unwrap().name.as_deref(),
1546 Some("shutting-down")
1547 );
1548 assert_eq!(
1549 sc.previous_state.as_ref().unwrap().name.as_deref(),
1550 Some("running")
1551 );
1552 }
1553
1554 #[tokio::test]
1555 async fn stop_instances_returns_state_changes() {
1556 let mut mock = MockClient::new();
1557 mock.expect_stop_instances().returning_bytes(ec2_response(
1558 "StopInstances",
1559 "<instancesSet>\
1560 <item>\
1561 <instanceId>i-0123456789abcdef0</instanceId>\
1562 <currentState><code>64</code><name>stopping</name></currentState>\
1563 <previousState><code>16</code><name>running</name></previousState>\
1564 </item>\
1565 </instancesSet>",
1566 ));
1567
1568 let client = AwsHttpClient::from_mock(mock);
1569 let body = crate::types::ec2::StopInstancesRequest {
1570 instance_ids: vec!["i-0123456789abcdef0".into()],
1571 };
1572 let response = client.ec2().stop_instances(&body).await.unwrap();
1573
1574 assert_eq!(response.stopping_instances.len(), 1);
1575 let sc = &response.stopping_instances[0];
1576 assert_eq!(sc.instance_id.as_deref(), Some("i-0123456789abcdef0"));
1577 assert_eq!(
1578 sc.current_state.as_ref().unwrap().name.as_deref(),
1579 Some("stopping")
1580 );
1581 }
1582
1583 #[tokio::test]
1584 async fn start_instances_returns_state_changes() {
1585 let mut mock = MockClient::new();
1586 mock.expect_start_instances().returning_bytes(ec2_response(
1587 "StartInstances",
1588 "<instancesSet>\
1589 <item>\
1590 <instanceId>i-0123456789abcdef0</instanceId>\
1591 <currentState><code>0</code><name>pending</name></currentState>\
1592 <previousState><code>80</code><name>stopped</name></previousState>\
1593 </item>\
1594 </instancesSet>",
1595 ));
1596
1597 let client = AwsHttpClient::from_mock(mock);
1598 let body = crate::types::ec2::StartInstancesRequest {
1599 instance_ids: vec!["i-0123456789abcdef0".into()],
1600 };
1601 let response = client.ec2().start_instances(&body).await.unwrap();
1602
1603 assert_eq!(response.starting_instances.len(), 1);
1604 let sc = &response.starting_instances[0];
1605 assert_eq!(sc.instance_id.as_deref(), Some("i-0123456789abcdef0"));
1606 assert_eq!(
1607 sc.current_state.as_ref().unwrap().name.as_deref(),
1608 Some("pending")
1609 );
1610 assert_eq!(
1611 sc.previous_state.as_ref().unwrap().name.as_deref(),
1612 Some("stopped")
1613 );
1614 }
1615
1616 #[tokio::test]
1617 async fn modify_instance_metadata_options_returns_response() {
1618 let mut mock = MockClient::new();
1619 mock.expect_modify_instance_metadata_options()
1620 .returning_bytes(ec2_response(
1621 "ModifyInstanceMetadataOptions",
1622 "<instanceId>i-0123456789abcdef0</instanceId>\
1623 <instanceMetadataOptions>\
1624 <httpTokens>required</httpTokens>\
1625 <httpEndpoint>enabled</httpEndpoint>\
1626 </instanceMetadataOptions>",
1627 ));
1628
1629 let client = AwsHttpClient::from_mock(mock);
1630 let body = crate::types::ec2::ModifyInstanceMetadataOptionsRequest {
1631 instance_id: "i-0123456789abcdef0".into(),
1632 http_tokens: Some("required".into()),
1633 http_endpoint: Some("enabled".into()),
1634 };
1635 let response = client
1636 .ec2()
1637 .modify_instance_metadata_options(&body)
1638 .await
1639 .unwrap();
1640
1641 assert_eq!(response.instance_id.as_deref(), Some("i-0123456789abcdef0"));
1642 let opts = response.instance_metadata_options.as_ref().unwrap();
1643 assert_eq!(opts.http_tokens.as_deref(), Some("required"));
1644 assert_eq!(opts.http_endpoint.as_deref(), Some("enabled"));
1645 }
1646
1647 #[tokio::test]
1648 async fn monitor_instances_returns_monitorings() {
1649 let mut mock = MockClient::new();
1650 mock.expect_monitor_instances()
1651 .returning_bytes(ec2_response(
1652 "MonitorInstances",
1653 "<instancesSet>\
1654 <item>\
1655 <instanceId>i-0123456789abcdef0</instanceId>\
1656 <monitoring><state>pending</state></monitoring>\
1657 </item>\
1658 </instancesSet>",
1659 ));
1660
1661 let client = AwsHttpClient::from_mock(mock);
1662 let body = crate::types::ec2::MonitorInstancesRequest {
1663 instance_ids: vec!["i-0123456789abcdef0".into()],
1664 };
1665 let response = client.ec2().monitor_instances(&body).await.unwrap();
1666
1667 assert_eq!(response.instance_monitorings.len(), 1);
1668 let mon = &response.instance_monitorings[0];
1669 assert_eq!(mon.instance_id.as_deref(), Some("i-0123456789abcdef0"));
1670 assert_eq!(
1671 mon.monitoring.as_ref().unwrap().state.as_deref(),
1672 Some("pending")
1673 );
1674 }
1675
1676 #[tokio::test]
1677 async fn associate_iam_instance_profile_returns_association() {
1678 let mut mock = MockClient::new();
1679 mock.expect_associate_iam_instance_profile()
1680 .returning_bytes(ec2_response(
1681 "AssociateIamInstanceProfile",
1682 "<iamInstanceProfileAssociation>\
1683 <associationId>iip-assoc-0abc123</associationId>\
1684 <instanceId>i-0123456789abcdef0</instanceId>\
1685 <state>associating</state>\
1686 </iamInstanceProfileAssociation>",
1687 ));
1688
1689 let client = AwsHttpClient::from_mock(mock);
1690 let body = crate::types::ec2::AssociateIamInstanceProfileRequest {
1691 iam_instance_profile: crate::types::ec2::IamInstanceProfileSpecification {
1692 arn: Some("arn:aws:iam::123456789012:instance-profile/test".into()),
1693 name: None,
1694 },
1695 instance_id: "i-0123456789abcdef0".into(),
1696 };
1697 let response = client
1698 .ec2()
1699 .associate_iam_instance_profile(&body)
1700 .await
1701 .unwrap();
1702
1703 let assoc = response.iam_instance_profile_association.as_ref().unwrap();
1704 assert_eq!(assoc.association_id.as_deref(), Some("iip-assoc-0abc123"));
1705 assert_eq!(assoc.instance_id.as_deref(), Some("i-0123456789abcdef0"));
1706 assert_eq!(assoc.state.as_deref(), Some("associating"));
1707 }
1708
1709 #[tokio::test]
1710 async fn create_tags_succeeds() {
1711 let mut mock = MockClient::new();
1712 mock.expect_create_tags()
1713 .returning_bytes(ec2_response("CreateTags", ""));
1714
1715 let client = AwsHttpClient::from_mock(mock);
1716 let body = crate::types::ec2::CreateTagsRequest {
1717 resources: vec!["i-0123456789abcdef0".into()],
1718 tags: vec![crate::types::ec2::Tag {
1719 key: Some("Name".into()),
1720 value: Some("test".into()),
1721 }],
1722 };
1723 let result = client.ec2().create_tags(&body).await;
1724 assert!(result.is_ok());
1725 }
1726
1727 #[tokio::test]
1728 async fn modify_instance_attribute_succeeds() {
1729 let mut mock = MockClient::new();
1730 mock.expect_modify_instance_attribute()
1731 .returning_bytes(ec2_response("ModifyInstanceAttribute", ""));
1732
1733 let client = AwsHttpClient::from_mock(mock);
1734 let body = crate::types::ec2::ModifyInstanceAttributeRequest {
1735 instance_id: "i-0123456789abcdef0".into(),
1736 };
1737 let result = client.ec2().modify_instance_attribute(&body).await;
1738 assert!(result.is_ok());
1739 }
1740
1741 #[tokio::test]
1742 async fn enable_snapshot_block_public_access_returns_state() {
1743 let mut mock = MockClient::new();
1744 mock.expect_enable_snapshot_block_public_access()
1745 .returning_bytes(ec2_response(
1746 "EnableSnapshotBlockPublicAccess",
1747 "<state>block-all-sharing</state>",
1748 ));
1749
1750 let client = AwsHttpClient::from_mock(mock);
1751 let body = crate::types::ec2::EnableSnapshotBlockPublicAccessRequest {
1752 state: "block-all-sharing".into(),
1753 };
1754 let response = client
1755 .ec2()
1756 .enable_snapshot_block_public_access(&body)
1757 .await
1758 .unwrap();
1759 assert_eq!(response.state.as_deref(), Some("block-all-sharing"));
1760 }
1761
1762 #[tokio::test]
1763 async fn enable_image_block_public_access_returns_state() {
1764 let mut mock = MockClient::new();
1765 mock.expect_enable_image_block_public_access()
1766 .returning_bytes(ec2_response(
1767 "EnableImageBlockPublicAccess",
1768 "<imageBlockPublicAccessState>block-new-sharing</imageBlockPublicAccessState>",
1769 ));
1770
1771 let client = AwsHttpClient::from_mock(mock);
1772 let body = crate::types::ec2::EnableImageBlockPublicAccessRequest {
1773 image_block_public_access_state: "block-new-sharing".into(),
1774 };
1775 let response = client
1776 .ec2()
1777 .enable_image_block_public_access(&body)
1778 .await
1779 .unwrap();
1780 assert_eq!(
1781 response.image_block_public_access_state.as_deref(),
1782 Some("block-new-sharing")
1783 );
1784 }
1785
1786 #[tokio::test]
1789 async fn describe_vpc_peering_connections_returns_connections() {
1790 let mut mock = MockClient::new();
1791 mock.expect_describe_vpc_peering_connections()
1792 .returning_bytes(ec2_response(
1793 "DescribeVpcPeeringConnections",
1794 "<vpcPeeringConnectionSet>\
1795 <item>\
1796 <vpcPeeringConnectionId>pcx-0a1b2c3d4e5f67890</vpcPeeringConnectionId>\
1797 <status><code>active</code><message>Active</message></status>\
1798 <accepterVpcInfo>\
1799 <vpcId>vpc-0accepter</vpcId>\
1800 <ownerId>111111111111</ownerId>\
1801 <cidrBlock>10.1.0.0/16</cidrBlock>\
1802 <region>eu-central-1</region>\
1803 </accepterVpcInfo>\
1804 <requesterVpcInfo>\
1805 <vpcId>vpc-0requester</vpcId>\
1806 <ownerId>222222222222</ownerId>\
1807 <cidrBlock>10.2.0.0/16</cidrBlock>\
1808 <region>eu-west-1</region>\
1809 </requesterVpcInfo>\
1810 </item>\
1811 </vpcPeeringConnectionSet>",
1812 ));
1813
1814 let client = AwsHttpClient::from_mock(mock);
1815 let body = crate::types::ec2::DescribeVpcPeeringConnectionsRequest::default();
1816 let response = client
1817 .ec2()
1818 .describe_vpc_peering_connections(&body)
1819 .await
1820 .unwrap();
1821
1822 assert_eq!(response.vpc_peering_connections.len(), 1);
1823 let pcx = &response.vpc_peering_connections[0];
1824 assert_eq!(
1825 pcx.vpc_peering_connection_id.as_deref(),
1826 Some("pcx-0a1b2c3d4e5f67890")
1827 );
1828 let status = pcx.status.as_ref().expect("status should be set");
1829 assert_eq!(status.code.as_deref(), Some("active"));
1830 let accepter = pcx.accepter_vpc_info.as_ref().expect("accepter_vpc_info");
1831 assert_eq!(accepter.vpc_id.as_deref(), Some("vpc-0accepter"));
1832 assert_eq!(accepter.cidr_block.as_deref(), Some("10.1.0.0/16"));
1833 let requester = pcx.requester_vpc_info.as_ref().expect("requester_vpc_info");
1834 assert_eq!(requester.vpc_id.as_deref(), Some("vpc-0requester"));
1835 assert_eq!(requester.cidr_block.as_deref(), Some("10.2.0.0/16"));
1836 }
1837
1838 #[tokio::test]
1839 async fn describe_vpc_peering_connections_handles_empty() {
1840 let mut mock = MockClient::new();
1841 mock.expect_describe_vpc_peering_connections()
1842 .returning_bytes(ec2_response(
1843 "DescribeVpcPeeringConnections",
1844 "<vpcPeeringConnectionSet/>",
1845 ));
1846
1847 let client = AwsHttpClient::from_mock(mock);
1848 let body = crate::types::ec2::DescribeVpcPeeringConnectionsRequest::default();
1849 let response = client
1850 .ec2()
1851 .describe_vpc_peering_connections(&body)
1852 .await
1853 .unwrap();
1854
1855 assert!(response.vpc_peering_connections.is_empty());
1856 }
1857}