1mod associations;
2mod automation;
3mod commands;
4mod compliance;
5mod documents;
6mod instances;
7mod inventory;
8mod maintenance;
9mod misc;
10mod ops;
11mod parameters;
12mod patches;
13mod resource_sync;
14mod sessions;
15mod tags;
16
17use async_trait::async_trait;
18use http::StatusCode;
19
20use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
21
22use crate::state::SharedSsmState;
23
24use fakecloud_secretsmanager::state::SharedSecretsManagerState;
25
26const PARAMETER_VERSION_LIMIT: i64 = 100;
27
28pub struct SsmService {
29 state: SharedSsmState,
30 secretsmanager_state: Option<SharedSecretsManagerState>,
31}
32
33impl SsmService {
34 pub fn new(state: SharedSsmState) -> Self {
35 Self {
36 state,
37 secretsmanager_state: None,
38 }
39 }
40
41 pub fn with_secretsmanager(mut self, sm_state: SharedSecretsManagerState) -> Self {
42 self.secretsmanager_state = Some(sm_state);
43 self
44 }
45}
46
47#[async_trait]
48impl AwsService for SsmService {
49 fn service_name(&self) -> &str {
50 "ssm"
51 }
52
53 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
54 match req.action.as_str() {
55 "PutParameter" => self.put_parameter(&req),
56 "GetParameter" => self.get_parameter(&req),
57 "GetParameters" => self.get_parameters(&req),
58 "GetParametersByPath" => self.get_parameters_by_path(&req),
59 "DeleteParameter" => self.delete_parameter(&req),
60 "DeleteParameters" => self.delete_parameters(&req),
61 "DescribeParameters" => self.describe_parameters(&req),
62 "GetParameterHistory" => self.get_parameter_history(&req),
63 "AddTagsToResource" => self.add_tags_to_resource(&req),
64 "RemoveTagsFromResource" => self.remove_tags_from_resource(&req),
65 "ListTagsForResource" => self.list_tags_for_resource(&req),
66 "LabelParameterVersion" => self.label_parameter_version(&req),
67 "UnlabelParameterVersion" => self.unlabel_parameter_version(&req),
68 "CreateDocument" => self.create_document(&req),
69 "GetDocument" => self.get_document(&req),
70 "DeleteDocument" => self.delete_document(&req),
71 "UpdateDocument" => self.update_document(&req),
72 "DescribeDocument" => self.describe_document(&req),
73 "UpdateDocumentDefaultVersion" => self.update_document_default_version(&req),
74 "ListDocuments" => self.list_documents(&req),
75 "DescribeDocumentPermission" => self.describe_document_permission(&req),
76 "ModifyDocumentPermission" => self.modify_document_permission(&req),
77 "SendCommand" => self.send_command(&req),
78 "ListCommands" => self.list_commands(&req),
79 "GetCommandInvocation" => self.get_command_invocation(&req),
80 "ListCommandInvocations" => self.list_command_invocations(&req),
81 "CancelCommand" => self.cancel_command(&req),
82 "CreateMaintenanceWindow" => self.create_maintenance_window(&req),
83 "DescribeMaintenanceWindows" => self.describe_maintenance_windows(&req),
84 "GetMaintenanceWindow" => self.get_maintenance_window(&req),
85 "DeleteMaintenanceWindow" => self.delete_maintenance_window(&req),
86 "UpdateMaintenanceWindow" => self.update_maintenance_window(&req),
87 "RegisterTargetWithMaintenanceWindow" => {
88 self.register_target_with_maintenance_window(&req)
89 }
90 "DeregisterTargetFromMaintenanceWindow" => {
91 self.deregister_target_from_maintenance_window(&req)
92 }
93 "DescribeMaintenanceWindowTargets" => self.describe_maintenance_window_targets(&req),
94 "RegisterTaskWithMaintenanceWindow" => self.register_task_with_maintenance_window(&req),
95 "DeregisterTaskFromMaintenanceWindow" => {
96 self.deregister_task_from_maintenance_window(&req)
97 }
98 "DescribeMaintenanceWindowTasks" => self.describe_maintenance_window_tasks(&req),
99 "CreatePatchBaseline" => self.create_patch_baseline(&req),
100 "DeletePatchBaseline" => self.delete_patch_baseline(&req),
101 "DescribePatchBaselines" => self.describe_patch_baselines(&req),
102 "GetPatchBaseline" => self.get_patch_baseline(&req),
103 "RegisterPatchBaselineForPatchGroup" => {
104 self.register_patch_baseline_for_patch_group(&req)
105 }
106 "DeregisterPatchBaselineForPatchGroup" => {
107 self.deregister_patch_baseline_for_patch_group(&req)
108 }
109 "GetPatchBaselineForPatchGroup" => self.get_patch_baseline_for_patch_group(&req),
110 "DescribePatchGroups" => self.describe_patch_groups(&req),
111 "CreateAssociation" => self.create_association(&req),
113 "DescribeAssociation" => self.describe_association(&req),
114 "DeleteAssociation" => self.delete_association(&req),
115 "ListAssociations" => self.list_associations(&req),
116 "UpdateAssociation" => self.update_association(&req),
117 "ListAssociationVersions" => self.list_association_versions(&req),
118 "UpdateAssociationStatus" => self.update_association_status(&req),
119 "StartAssociationsOnce" => self.start_associations_once(&req),
120 "CreateAssociationBatch" => self.create_association_batch(&req),
121 "DescribeAssociationExecutions" => self.describe_association_executions(&req),
122 "DescribeAssociationExecutionTargets" => {
123 self.describe_association_execution_targets(&req)
124 }
125 "CreateOpsItem" => self.create_ops_item(&req),
127 "GetOpsItem" => self.get_ops_item(&req),
128 "UpdateOpsItem" => self.update_ops_item(&req),
129 "DeleteOpsItem" => self.delete_ops_item(&req),
130 "DescribeOpsItems" => self.describe_ops_items(&req),
131 "ListDocumentVersions" => self.list_document_versions(&req),
133 "ListDocumentMetadataHistory" => self.list_document_metadata_history(&req),
134 "UpdateDocumentMetadata" => self.update_document_metadata(&req),
135 "PutResourcePolicy" => self.put_resource_policy(&req),
137 "GetResourcePolicies" => self.get_resource_policies(&req),
138 "DeleteResourcePolicy" => self.delete_resource_policy(&req),
139 "PutInventory" => self.put_inventory(&req),
141 "GetInventory" => self.get_inventory(&req),
142 "GetInventorySchema" => self.get_inventory_schema(&req),
143 "ListInventoryEntries" => self.list_inventory_entries(&req),
144 "DeleteInventory" => self.delete_inventory(&req),
145 "DescribeInventoryDeletions" => self.describe_inventory_deletions(&req),
146 "PutComplianceItems" => self.put_compliance_items(&req),
148 "ListComplianceItems" => self.list_compliance_items(&req),
149 "ListComplianceSummaries" => self.list_compliance_summaries(&req),
150 "ListResourceComplianceSummaries" => self.list_resource_compliance_summaries(&req),
151 "UpdateMaintenanceWindowTarget" => self.update_maintenance_window_target(&req),
153 "UpdateMaintenanceWindowTask" => self.update_maintenance_window_task(&req),
154 "GetMaintenanceWindowTask" => self.get_maintenance_window_task(&req),
155 "GetMaintenanceWindowExecution" => self.get_maintenance_window_execution(&req),
156 "GetMaintenanceWindowExecutionTask" => self.get_maintenance_window_execution_task(&req),
157 "GetMaintenanceWindowExecutionTaskInvocation" => {
158 self.get_maintenance_window_execution_task_invocation(&req)
159 }
160 "DescribeMaintenanceWindowExecutions" => {
161 self.describe_maintenance_window_executions(&req)
162 }
163 "DescribeMaintenanceWindowExecutionTasks" => {
164 self.describe_maintenance_window_execution_tasks(&req)
165 }
166 "DescribeMaintenanceWindowExecutionTaskInvocations" => {
167 self.describe_maintenance_window_execution_task_invocations(&req)
168 }
169 "DescribeMaintenanceWindowSchedule" => self.describe_maintenance_window_schedule(&req),
170 "DescribeMaintenanceWindowsForTarget" => {
171 self.describe_maintenance_windows_for_target(&req)
172 }
173 "CancelMaintenanceWindowExecution" => self.cancel_maintenance_window_execution(&req),
174 "UpdatePatchBaseline" => self.update_patch_baseline(&req),
176 "DescribeInstancePatchStates" => self.describe_instance_patch_states(&req),
177 "DescribeInstancePatchStatesForPatchGroup" => {
178 self.describe_instance_patch_states_for_patch_group(&req)
179 }
180 "DescribeInstancePatches" => self.describe_instance_patches(&req),
181 "DescribeEffectivePatchesForPatchBaseline" => {
182 self.describe_effective_patches_for_patch_baseline(&req)
183 }
184 "GetDeployablePatchSnapshotForInstance" => {
185 self.get_deployable_patch_snapshot_for_instance(&req)
186 }
187 "CreateResourceDataSync" => self.create_resource_data_sync(&req),
189 "DeleteResourceDataSync" => self.delete_resource_data_sync(&req),
190 "ListResourceDataSync" => self.list_resource_data_sync(&req),
191 "UpdateResourceDataSync" => self.update_resource_data_sync(&req),
192 "AssociateOpsItemRelatedItem" => self.associate_ops_item_related_item(&req),
194 "DisassociateOpsItemRelatedItem" => self.disassociate_ops_item_related_item(&req),
195 "ListOpsItemRelatedItems" => self.list_ops_item_related_items(&req),
196 "ListOpsItemEvents" => self.list_ops_item_events(&req),
197 "CreateOpsMetadata" => self.create_ops_metadata(&req),
199 "GetOpsMetadata" => self.get_ops_metadata(&req),
200 "UpdateOpsMetadata" => self.update_ops_metadata(&req),
201 "DeleteOpsMetadata" => self.delete_ops_metadata(&req),
202 "ListOpsMetadata" => self.list_ops_metadata(&req),
203 "GetOpsSummary" => self.get_ops_summary(&req),
205 "StartAutomationExecution" => self.start_automation_execution(&req),
207 "StopAutomationExecution" => self.stop_automation_execution(&req),
208 "GetAutomationExecution" => self.get_automation_execution(&req),
209 "DescribeAutomationExecutions" => self.describe_automation_executions(&req),
210 "DescribeAutomationStepExecutions" => self.describe_automation_step_executions(&req),
211 "SendAutomationSignal" => self.send_automation_signal(&req),
212 "StartChangeRequestExecution" => self.start_change_request_execution(&req),
213 "StartExecutionPreview" => self.start_execution_preview(&req),
214 "GetExecutionPreview" => self.get_execution_preview(&req),
215 "StartSession" => self.start_session(&req),
217 "ResumeSession" => self.resume_session(&req),
218 "TerminateSession" => self.terminate_session(&req),
219 "DescribeSessions" => self.describe_sessions(&req),
220 "StartAccessRequest" => self.start_access_request(&req),
221 "GetAccessToken" => self.get_access_token(&req),
222 "CreateActivation" => self.create_activation(&req),
224 "DeleteActivation" => self.delete_activation(&req),
225 "DescribeActivations" => self.describe_activations(&req),
226 "DeregisterManagedInstance" => self.deregister_managed_instance(&req),
227 "DescribeInstanceInformation" => self.describe_instance_information(&req),
228 "DescribeInstanceProperties" => self.describe_instance_properties(&req),
229 "UpdateManagedInstanceRole" => self.update_managed_instance_role(&req),
230 "ListNodes" => self.list_nodes(&req),
232 "ListNodesSummary" => self.list_nodes_summary(&req),
233 "DescribeEffectiveInstanceAssociations" => {
234 self.describe_effective_instance_associations(&req)
235 }
236 "DescribeInstanceAssociationsStatus" => {
237 self.describe_instance_associations_status(&req)
238 }
239 "GetConnectionStatus" => self.get_connection_status(&req),
241 "GetCalendarState" => self.get_calendar_state(&req),
242 "DescribePatchGroupState" => self.describe_patch_group_state(&req),
243 "DescribePatchProperties" => self.describe_patch_properties(&req),
244 "GetDefaultPatchBaseline" => self.get_default_patch_baseline(&req),
245 "RegisterDefaultPatchBaseline" => self.register_default_patch_baseline(&req),
246 "DescribeAvailablePatches" => self.describe_available_patches(&req),
247 "GetServiceSetting" => self.get_service_setting(&req),
248 "ResetServiceSetting" => self.reset_service_setting(&req),
249 "UpdateServiceSetting" => self.update_service_setting(&req),
250 _ => Err(AwsServiceError::action_not_implemented("ssm", &req.action)),
251 }
252 }
253
254 fn supported_actions(&self) -> &[&str] {
255 &[
256 "PutParameter",
257 "GetParameter",
258 "GetParameters",
259 "GetParametersByPath",
260 "DeleteParameter",
261 "DeleteParameters",
262 "DescribeParameters",
263 "GetParameterHistory",
264 "AddTagsToResource",
265 "RemoveTagsFromResource",
266 "ListTagsForResource",
267 "LabelParameterVersion",
268 "UnlabelParameterVersion",
269 "CreateDocument",
270 "GetDocument",
271 "DeleteDocument",
272 "UpdateDocument",
273 "DescribeDocument",
274 "UpdateDocumentDefaultVersion",
275 "ListDocuments",
276 "DescribeDocumentPermission",
277 "ModifyDocumentPermission",
278 "SendCommand",
279 "ListCommands",
280 "GetCommandInvocation",
281 "ListCommandInvocations",
282 "CancelCommand",
283 "CreateMaintenanceWindow",
284 "DescribeMaintenanceWindows",
285 "GetMaintenanceWindow",
286 "DeleteMaintenanceWindow",
287 "UpdateMaintenanceWindow",
288 "RegisterTargetWithMaintenanceWindow",
289 "DeregisterTargetFromMaintenanceWindow",
290 "DescribeMaintenanceWindowTargets",
291 "RegisterTaskWithMaintenanceWindow",
292 "DeregisterTaskFromMaintenanceWindow",
293 "DescribeMaintenanceWindowTasks",
294 "CreatePatchBaseline",
295 "DeletePatchBaseline",
296 "DescribePatchBaselines",
297 "GetPatchBaseline",
298 "RegisterPatchBaselineForPatchGroup",
299 "DeregisterPatchBaselineForPatchGroup",
300 "GetPatchBaselineForPatchGroup",
301 "DescribePatchGroups",
302 "CreateAssociation",
304 "DescribeAssociation",
305 "DeleteAssociation",
306 "ListAssociations",
307 "UpdateAssociation",
308 "ListAssociationVersions",
309 "UpdateAssociationStatus",
310 "StartAssociationsOnce",
311 "CreateAssociationBatch",
312 "DescribeAssociationExecutions",
313 "DescribeAssociationExecutionTargets",
314 "CreateOpsItem",
316 "GetOpsItem",
317 "UpdateOpsItem",
318 "DeleteOpsItem",
319 "DescribeOpsItems",
320 "ListDocumentVersions",
322 "ListDocumentMetadataHistory",
323 "UpdateDocumentMetadata",
324 "PutResourcePolicy",
326 "GetResourcePolicies",
327 "DeleteResourcePolicy",
328 "PutInventory",
330 "GetInventory",
331 "GetInventorySchema",
332 "ListInventoryEntries",
333 "DeleteInventory",
334 "DescribeInventoryDeletions",
335 "PutComplianceItems",
337 "ListComplianceItems",
338 "ListComplianceSummaries",
339 "ListResourceComplianceSummaries",
340 "UpdateMaintenanceWindowTarget",
342 "UpdateMaintenanceWindowTask",
343 "GetMaintenanceWindowTask",
344 "GetMaintenanceWindowExecution",
345 "GetMaintenanceWindowExecutionTask",
346 "GetMaintenanceWindowExecutionTaskInvocation",
347 "DescribeMaintenanceWindowExecutions",
348 "DescribeMaintenanceWindowExecutionTasks",
349 "DescribeMaintenanceWindowExecutionTaskInvocations",
350 "DescribeMaintenanceWindowSchedule",
351 "DescribeMaintenanceWindowsForTarget",
352 "CancelMaintenanceWindowExecution",
353 "UpdatePatchBaseline",
355 "DescribeInstancePatchStates",
356 "DescribeInstancePatchStatesForPatchGroup",
357 "DescribeInstancePatches",
358 "DescribeEffectivePatchesForPatchBaseline",
359 "GetDeployablePatchSnapshotForInstance",
360 "CreateResourceDataSync",
362 "DeleteResourceDataSync",
363 "ListResourceDataSync",
364 "UpdateResourceDataSync",
365 "AssociateOpsItemRelatedItem",
367 "DisassociateOpsItemRelatedItem",
368 "ListOpsItemRelatedItems",
369 "ListOpsItemEvents",
370 "CreateOpsMetadata",
372 "GetOpsMetadata",
373 "UpdateOpsMetadata",
374 "DeleteOpsMetadata",
375 "ListOpsMetadata",
376 "GetOpsSummary",
378 "StartAutomationExecution",
380 "StopAutomationExecution",
381 "GetAutomationExecution",
382 "DescribeAutomationExecutions",
383 "DescribeAutomationStepExecutions",
384 "SendAutomationSignal",
385 "StartChangeRequestExecution",
386 "StartExecutionPreview",
387 "GetExecutionPreview",
388 "StartSession",
390 "ResumeSession",
391 "TerminateSession",
392 "DescribeSessions",
393 "StartAccessRequest",
394 "GetAccessToken",
395 "CreateActivation",
397 "DeleteActivation",
398 "DescribeActivations",
399 "DeregisterManagedInstance",
400 "DescribeInstanceInformation",
401 "DescribeInstanceProperties",
402 "UpdateManagedInstanceRole",
403 "ListNodes",
405 "ListNodesSummary",
406 "DescribeEffectiveInstanceAssociations",
407 "DescribeInstanceAssociationsStatus",
408 "GetConnectionStatus",
410 "GetCalendarState",
411 "DescribePatchGroupState",
412 "DescribePatchProperties",
413 "GetDefaultPatchBaseline",
414 "RegisterDefaultPatchBaseline",
415 "DescribeAvailablePatches",
416 "GetServiceSetting",
417 "ResetServiceSetting",
418 "UpdateServiceSetting",
419 ]
420 }
421}
422
423fn missing(name: &str) -> AwsServiceError {
424 AwsServiceError::aws_error(
425 StatusCode::BAD_REQUEST,
426 "ValidationException",
427 format!("The request must contain the parameter {name}"),
428 )
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434 use parking_lot::RwLock;
435 use serde_json::{json, Value};
436 use std::collections::HashMap;
437 use std::sync::Arc;
438
439 fn make_service() -> SsmService {
440 let state: SharedSsmState = Arc::new(RwLock::new(crate::state::SsmState::new(
441 "123456789012",
442 "us-east-1",
443 )));
444 SsmService::new(state)
445 }
446
447 fn make_request(action: &str, body: Value) -> AwsRequest {
448 AwsRequest {
449 service: "ssm".to_string(),
450 action: action.to_string(),
451 region: "us-east-1".to_string(),
452 account_id: "123456789012".to_string(),
453 request_id: "test-id".to_string(),
454 headers: http::HeaderMap::new(),
455 query_params: HashMap::new(),
456 body: serde_json::to_vec(&body).unwrap().into(),
457 path_segments: vec![],
458 raw_path: "/".to_string(),
459 raw_query: String::new(),
460 method: http::Method::POST,
461 is_query_protocol: false,
462 access_key_id: None,
463 }
464 }
465
466 fn send_command(svc: &SsmService, doc_name: &str) -> String {
467 let req = make_request(
468 "SendCommand",
469 json!({
470 "DocumentName": doc_name,
471 "InstanceIds": ["i-1234567890abcdef0"]
472 }),
473 );
474 let resp = svc.send_command(&req).unwrap();
475 let body: Value = serde_json::from_slice(&resp.body).unwrap();
476 body["Command"]["CommandId"].as_str().unwrap().to_string()
477 }
478
479 #[test]
480 fn list_commands_pagination() {
481 let svc = make_service();
482
483 let mut command_ids = Vec::new();
485 for i in 0..3 {
486 command_ids.push(send_command(&svc, &format!("AWS-RunShellScript-{i}")));
487 }
488
489 let req = make_request("ListCommands", json!({ "MaxResults": 1 }));
491 let resp = svc.list_commands(&req).unwrap();
492 let body: Value = serde_json::from_slice(&resp.body).unwrap();
493 assert_eq!(body["Commands"].as_array().unwrap().len(), 1);
494 let token = body["NextToken"].as_str().unwrap();
495
496 let req = make_request(
498 "ListCommands",
499 json!({ "MaxResults": 1, "NextToken": token }),
500 );
501 let resp = svc.list_commands(&req).unwrap();
502 let body: Value = serde_json::from_slice(&resp.body).unwrap();
503 assert_eq!(body["Commands"].as_array().unwrap().len(), 1);
504 let token = body["NextToken"].as_str().unwrap();
505
506 let req = make_request(
508 "ListCommands",
509 json!({ "MaxResults": 1, "NextToken": token }),
510 );
511 let resp = svc.list_commands(&req).unwrap();
512 let body: Value = serde_json::from_slice(&resp.body).unwrap();
513 assert_eq!(body["Commands"].as_array().unwrap().len(), 1);
514 assert!(body.get("NextToken").is_none() || body["NextToken"].is_null());
515 }
516
517 #[test]
518 fn send_command_response_omits_non_shape_fields() {
519 let svc = make_service();
520
521 let req = make_request(
523 "CreateDocument",
524 json!({
525 "Name": "TestDoc",
526 "Content": "{\"schemaVersion\":\"2.2\",\"mainSteps\":[]}",
527 "DocumentType": "Command"
528 }),
529 );
530 svc.create_document(&req).unwrap();
531
532 let req = make_request(
533 "SendCommand",
534 json!({
535 "DocumentName": "TestDoc",
536 "InstanceIds": ["i-1234567890abcdef0"],
537 "DocumentHash": "abc123hash",
538 "DocumentHashType": "Sha256",
539 "ServiceRoleArn": "arn:aws:iam::123456789012:role/MyRole"
540 }),
541 );
542 let resp = svc.send_command(&req).unwrap();
543 let body: Value = serde_json::from_slice(&resp.body).unwrap();
544 let cmd = &body["Command"];
545
546 assert!(
548 !cmd.as_object().unwrap().contains_key("DocumentHash"),
549 "DocumentHash should not be in SendCommand response"
550 );
551 assert!(
552 !cmd.as_object().unwrap().contains_key("DocumentHashType"),
553 "DocumentHashType should not be in SendCommand response"
554 );
555 assert!(
556 !cmd.as_object().unwrap().contains_key("ServiceRoleArn"),
557 "ServiceRoleArn should not be in SendCommand response"
558 );
559
560 assert!(cmd["CommandId"].is_string());
562 assert_eq!(cmd["DocumentName"].as_str().unwrap(), "TestDoc");
563 }
564
565 #[test]
566 fn describe_maintenance_windows_pagination() {
567 let svc = make_service();
568
569 for i in 0..11 {
572 let req = make_request(
573 "CreateMaintenanceWindow",
574 json!({
575 "Name": format!("test-window-{i:02}"),
576 "Schedule": "cron(0 2 ? * SUN *)",
577 "Duration": 3,
578 "Cutoff": 1,
579 "AllowUnassociatedTargets": true
580 }),
581 );
582 svc.create_maintenance_window(&req).unwrap();
583 }
584
585 let req = make_request("DescribeMaintenanceWindows", json!({ "MaxResults": 10 }));
587 let resp = svc.describe_maintenance_windows(&req).unwrap();
588 let body: Value = serde_json::from_slice(&resp.body).unwrap();
589 assert_eq!(body["WindowIdentities"].as_array().unwrap().len(), 10);
590 let token = body["NextToken"].as_str().unwrap();
591
592 let req = make_request(
594 "DescribeMaintenanceWindows",
595 json!({ "MaxResults": 10, "NextToken": token }),
596 );
597 let resp = svc.describe_maintenance_windows(&req).unwrap();
598 let body: Value = serde_json::from_slice(&resp.body).unwrap();
599 assert_eq!(body["WindowIdentities"].as_array().unwrap().len(), 1);
600 assert!(body.get("NextToken").is_none() || body["NextToken"].is_null());
601 }
602
603 #[test]
606 fn association_crud() {
607 let svc = make_service();
608
609 let req = make_request(
611 "CreateAssociation",
612 json!({
613 "Name": "AWS-RunShellScript",
614 "Targets": [{"Key": "InstanceIds", "Values": ["i-1234567890abcdef0"]}],
615 "ScheduleExpression": "rate(1 hour)",
616 "AssociationName": "my-assoc",
617 }),
618 );
619 let resp = svc.create_association(&req).unwrap();
620 let body: Value = serde_json::from_slice(&resp.body).unwrap();
621 let assoc_id = body["AssociationDescription"]["AssociationId"]
622 .as_str()
623 .unwrap()
624 .to_string();
625 assert_eq!(
626 body["AssociationDescription"]["Name"].as_str().unwrap(),
627 "AWS-RunShellScript"
628 );
629
630 let req = make_request("DescribeAssociation", json!({ "AssociationId": assoc_id }));
632 let resp = svc.describe_association(&req).unwrap();
633 let body: Value = serde_json::from_slice(&resp.body).unwrap();
634 assert_eq!(
635 body["AssociationDescription"]["AssociationName"]
636 .as_str()
637 .unwrap(),
638 "my-assoc"
639 );
640
641 let req = make_request("ListAssociations", json!({}));
643 let resp = svc.list_associations(&req).unwrap();
644 let body: Value = serde_json::from_slice(&resp.body).unwrap();
645 assert_eq!(body["Associations"].as_array().unwrap().len(), 1);
646
647 let req = make_request(
649 "UpdateAssociation",
650 json!({
651 "AssociationId": assoc_id,
652 "AssociationName": "updated-assoc",
653 }),
654 );
655 let resp = svc.update_association(&req).unwrap();
656 let body: Value = serde_json::from_slice(&resp.body).unwrap();
657 assert_eq!(
658 body["AssociationDescription"]["AssociationName"]
659 .as_str()
660 .unwrap(),
661 "updated-assoc"
662 );
663
664 let req = make_request(
666 "ListAssociationVersions",
667 json!({ "AssociationId": assoc_id }),
668 );
669 let resp = svc.list_association_versions(&req).unwrap();
670 let body: Value = serde_json::from_slice(&resp.body).unwrap();
671 assert_eq!(body["AssociationVersions"].as_array().unwrap().len(), 2);
672
673 let req = make_request("DeleteAssociation", json!({ "AssociationId": assoc_id }));
675 svc.delete_association(&req).unwrap();
676
677 let req = make_request("DescribeAssociation", json!({ "AssociationId": assoc_id }));
679 assert!(svc.describe_association(&req).is_err());
680 }
681
682 #[test]
683 fn association_batch_create() {
684 let svc = make_service();
685 let req = make_request(
686 "CreateAssociationBatch",
687 json!({
688 "Entries": [
689 {"Name": "AWS-RunShellScript", "Targets": [{"Key": "InstanceIds", "Values": ["i-001"]}]},
690 {"Name": "AWS-RunShellScript", "Targets": [{"Key": "InstanceIds", "Values": ["i-002"]}]},
691 ]
692 }),
693 );
694 let resp = svc.create_association_batch(&req).unwrap();
695 let body: Value = serde_json::from_slice(&resp.body).unwrap();
696 assert_eq!(body["Successful"].as_array().unwrap().len(), 2);
697 assert!(body["Failed"].as_array().unwrap().is_empty());
698 }
699
700 #[test]
701 fn start_associations_once_noop() {
702 let svc = make_service();
703 let req = make_request(
704 "StartAssociationsOnce",
705 json!({ "AssociationIds": ["fake-id"] }),
706 );
707 svc.start_associations_once(&req).unwrap();
708 }
709
710 #[test]
713 fn ops_item_crud() {
714 let svc = make_service();
715
716 let req = make_request(
718 "CreateOpsItem",
719 json!({
720 "Title": "Test OpsItem",
721 "Source": "test",
722 "Description": "A test ops item",
723 }),
724 );
725 let resp = svc.create_ops_item(&req).unwrap();
726 let body: Value = serde_json::from_slice(&resp.body).unwrap();
727 let ops_item_id = body["OpsItemId"].as_str().unwrap().to_string();
728
729 let req = make_request("GetOpsItem", json!({ "OpsItemId": ops_item_id }));
731 let resp = svc.get_ops_item(&req).unwrap();
732 let body: Value = serde_json::from_slice(&resp.body).unwrap();
733 assert_eq!(body["OpsItem"]["Title"].as_str().unwrap(), "Test OpsItem");
734 assert_eq!(body["OpsItem"]["Status"].as_str().unwrap(), "Open");
735
736 let req = make_request(
738 "UpdateOpsItem",
739 json!({
740 "OpsItemId": ops_item_id,
741 "Title": "Updated OpsItem",
742 "Status": "Resolved",
743 }),
744 );
745 svc.update_ops_item(&req).unwrap();
746
747 let req = make_request("GetOpsItem", json!({ "OpsItemId": ops_item_id }));
749 let resp = svc.get_ops_item(&req).unwrap();
750 let body: Value = serde_json::from_slice(&resp.body).unwrap();
751 assert_eq!(
752 body["OpsItem"]["Title"].as_str().unwrap(),
753 "Updated OpsItem"
754 );
755 assert_eq!(body["OpsItem"]["Status"].as_str().unwrap(), "Resolved");
756
757 let req = make_request("DescribeOpsItems", json!({}));
759 let resp = svc.describe_ops_items(&req).unwrap();
760 let body: Value = serde_json::from_slice(&resp.body).unwrap();
761 assert_eq!(body["OpsItemSummaries"].as_array().unwrap().len(), 1);
762
763 let req = make_request("DeleteOpsItem", json!({ "OpsItemId": ops_item_id }));
765 svc.delete_ops_item(&req).unwrap();
766
767 let req = make_request("GetOpsItem", json!({ "OpsItemId": ops_item_id }));
769 assert!(svc.get_ops_item(&req).is_err());
770 }
771
772 #[test]
775 fn resource_policy_crud() {
776 let svc = make_service();
777 let resource_arn = "arn:aws:ssm:us-east-1:123456789012:parameter/test";
778
779 let req = make_request(
781 "PutResourcePolicy",
782 json!({
783 "ResourceArn": resource_arn,
784 "Policy": r#"{"Version":"2012-10-17","Statement":[]}"#,
785 }),
786 );
787 let resp = svc.put_resource_policy(&req).unwrap();
788 let body: Value = serde_json::from_slice(&resp.body).unwrap();
789 let policy_id = body["PolicyId"].as_str().unwrap().to_string();
790 let policy_hash = body["PolicyHash"].as_str().unwrap().to_string();
791
792 let req = make_request(
794 "GetResourcePolicies",
795 json!({ "ResourceArn": resource_arn }),
796 );
797 let resp = svc.get_resource_policies(&req).unwrap();
798 let body: Value = serde_json::from_slice(&resp.body).unwrap();
799 assert_eq!(body["Policies"].as_array().unwrap().len(), 1);
800
801 let req = make_request(
803 "DeleteResourcePolicy",
804 json!({
805 "ResourceArn": resource_arn,
806 "PolicyId": policy_id,
807 "PolicyHash": policy_hash,
808 }),
809 );
810 svc.delete_resource_policy(&req).unwrap();
811
812 let req = make_request(
814 "GetResourcePolicies",
815 json!({ "ResourceArn": resource_arn }),
816 );
817 let resp = svc.get_resource_policies(&req).unwrap();
818 let body: Value = serde_json::from_slice(&resp.body).unwrap();
819 assert!(body["Policies"].as_array().unwrap().is_empty());
820 }
821
822 #[test]
825 fn get_connection_status_returns_connected() {
826 let svc = make_service();
827 let req = make_request(
828 "GetConnectionStatus",
829 json!({ "Target": "i-1234567890abcdef0" }),
830 );
831 let resp = svc.get_connection_status(&req).unwrap();
832 let body: Value = serde_json::from_slice(&resp.body).unwrap();
833 assert_eq!(body["Status"].as_str().unwrap(), "connected");
834 }
835
836 #[test]
837 fn get_calendar_state_returns_open() {
838 let svc = make_service();
839 let req = make_request(
840 "GetCalendarState",
841 json!({ "CalendarNames": ["arn:aws:ssm:us-east-1:123456789012:document/cal"] }),
842 );
843 let resp = svc.get_calendar_state(&req).unwrap();
844 let body: Value = serde_json::from_slice(&resp.body).unwrap();
845 assert_eq!(body["State"].as_str().unwrap(), "OPEN");
846 }
847
848 #[test]
849 fn service_setting_crud() {
850 let svc = make_service();
851
852 let req = make_request(
854 "GetServiceSetting",
855 json!({ "SettingId": "/ssm/parameter-store/high-throughput-enabled" }),
856 );
857 let resp = svc.get_service_setting(&req).unwrap();
858 let body: Value = serde_json::from_slice(&resp.body).unwrap();
859 assert_eq!(
860 body["ServiceSetting"]["Status"].as_str().unwrap(),
861 "Default"
862 );
863
864 let req = make_request(
866 "UpdateServiceSetting",
867 json!({
868 "SettingId": "/ssm/parameter-store/high-throughput-enabled",
869 "SettingValue": "true",
870 }),
871 );
872 svc.update_service_setting(&req).unwrap();
873
874 let req = make_request(
876 "GetServiceSetting",
877 json!({ "SettingId": "/ssm/parameter-store/high-throughput-enabled" }),
878 );
879 let resp = svc.get_service_setting(&req).unwrap();
880 let body: Value = serde_json::from_slice(&resp.body).unwrap();
881 assert_eq!(
882 body["ServiceSetting"]["Status"].as_str().unwrap(),
883 "Customized"
884 );
885 assert_eq!(
886 body["ServiceSetting"]["SettingValue"].as_str().unwrap(),
887 "true"
888 );
889
890 let req = make_request(
892 "ResetServiceSetting",
893 json!({ "SettingId": "/ssm/parameter-store/high-throughput-enabled" }),
894 );
895 svc.reset_service_setting(&req).unwrap();
896
897 let req = make_request(
899 "GetServiceSetting",
900 json!({ "SettingId": "/ssm/parameter-store/high-throughput-enabled" }),
901 );
902 let resp = svc.get_service_setting(&req).unwrap();
903 let body: Value = serde_json::from_slice(&resp.body).unwrap();
904 assert_eq!(
905 body["ServiceSetting"]["Status"].as_str().unwrap(),
906 "Default"
907 );
908 }
909
910 #[test]
911 fn list_document_versions_works() {
912 let svc = make_service();
913
914 let req = make_request(
916 "CreateDocument",
917 json!({
918 "Name": "TestDocVer",
919 "Content": r#"{"schemaVersion":"2.2","mainSteps":[]}"#,
920 "DocumentType": "Command",
921 }),
922 );
923 svc.create_document(&req).unwrap();
924
925 let req = make_request("ListDocumentVersions", json!({ "Name": "TestDocVer" }));
927 let resp = svc.list_document_versions(&req).unwrap();
928 let body: Value = serde_json::from_slice(&resp.body).unwrap();
929 assert!(!body["DocumentVersions"].as_array().unwrap().is_empty());
930 }
931
932 #[test]
933 fn describe_patch_group_state_returns_zeros() {
934 let svc = make_service();
935 let req = make_request(
936 "DescribePatchGroupState",
937 json!({ "PatchGroup": "test-group" }),
938 );
939 let resp = svc.describe_patch_group_state(&req).unwrap();
940 let body: Value = serde_json::from_slice(&resp.body).unwrap();
941 assert_eq!(body["Instances"].as_i64().unwrap(), 0);
942 }
943
944 #[test]
945 fn get_default_patch_baseline_works() {
946 let svc = make_service();
947 let req = make_request(
948 "GetDefaultPatchBaseline",
949 json!({ "OperatingSystem": "WINDOWS" }),
950 );
951 let resp = svc.get_default_patch_baseline(&req).unwrap();
952 let body: Value = serde_json::from_slice(&resp.body).unwrap();
953 assert!(body["BaselineId"].is_string());
954 }
955
956 #[test]
957 fn describe_available_patches_returns_empty() {
958 let svc = make_service();
959 let req = make_request("DescribeAvailablePatches", json!({}));
960 let resp = svc.describe_available_patches(&req).unwrap();
961 let body: Value = serde_json::from_slice(&resp.body).unwrap();
962 assert!(body["Patches"].as_array().unwrap().is_empty());
963 }
964
965 #[test]
966 fn describe_patch_properties_returns_empty() {
967 let svc = make_service();
968 let req = make_request(
969 "DescribePatchProperties",
970 json!({ "OperatingSystem": "WINDOWS", "Property": "PRODUCT" }),
971 );
972 let resp = svc.describe_patch_properties(&req).unwrap();
973 let body: Value = serde_json::from_slice(&resp.body).unwrap();
974 assert!(body["Properties"].as_array().unwrap().is_empty());
975 }
976
977 #[test]
980 fn inventory_lifecycle() {
981 let svc = make_service();
982
983 let req = make_request(
985 "PutInventory",
986 json!({
987 "InstanceId": "i-1234567890abcdef0",
988 "Items": [{
989 "TypeName": "AWS:Application",
990 "SchemaVersion": "1.1",
991 "CaptureTime": "2024-01-01T00:00:00Z",
992 "Content": [
993 {"Name": "TestApp", "Version": "1.0"},
994 {"Name": "AnotherApp", "Version": "2.0"},
995 ]
996 }]
997 }),
998 );
999 svc.put_inventory(&req).unwrap();
1000
1001 let req = make_request("GetInventory", json!({}));
1003 let resp = svc.get_inventory(&req).unwrap();
1004 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1005 assert_eq!(body["Entities"].as_array().unwrap().len(), 1);
1006 assert_eq!(
1007 body["Entities"][0]["Id"].as_str().unwrap(),
1008 "i-1234567890abcdef0"
1009 );
1010
1011 let req = make_request(
1013 "ListInventoryEntries",
1014 json!({
1015 "InstanceId": "i-1234567890abcdef0",
1016 "TypeName": "AWS:Application",
1017 }),
1018 );
1019 let resp = svc.list_inventory_entries(&req).unwrap();
1020 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1021 assert_eq!(body["Entries"].as_array().unwrap().len(), 2);
1022 assert_eq!(body["TypeName"].as_str().unwrap(), "AWS:Application");
1023
1024 let req = make_request("GetInventorySchema", json!({}));
1026 let resp = svc.get_inventory_schema(&req).unwrap();
1027 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1028 assert!(!body["Schemas"].as_array().unwrap().is_empty());
1029
1030 let req = make_request("DeleteInventory", json!({ "TypeName": "AWS:Application" }));
1032 let resp = svc.delete_inventory(&req).unwrap();
1033 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1034 assert!(body["DeletionId"].is_string());
1035
1036 let req = make_request("DescribeInventoryDeletions", json!({}));
1038 let resp = svc.describe_inventory_deletions(&req).unwrap();
1039 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1040 assert_eq!(body["InventoryDeletions"].as_array().unwrap().len(), 1);
1041
1042 let req = make_request(
1044 "ListInventoryEntries",
1045 json!({
1046 "InstanceId": "i-1234567890abcdef0",
1047 "TypeName": "AWS:Application",
1048 }),
1049 );
1050 let resp = svc.list_inventory_entries(&req).unwrap();
1051 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1052 assert!(body["Entries"].as_array().unwrap().is_empty());
1053 }
1054
1055 #[test]
1058 fn compliance_lifecycle() {
1059 let svc = make_service();
1060
1061 let req = make_request(
1063 "PutComplianceItems",
1064 json!({
1065 "ResourceId": "i-1234567890abcdef0",
1066 "ResourceType": "ManagedInstance",
1067 "ComplianceType": "Custom:PatchTest",
1068 "ExecutionSummary": {
1069 "ExecutionTime": "2024-01-01T00:00:00Z",
1070 },
1071 "Items": [
1072 {
1073 "Id": "patch-1",
1074 "Title": "Security patch 1",
1075 "Severity": "CRITICAL",
1076 "Status": "COMPLIANT",
1077 },
1078 {
1079 "Id": "patch-2",
1080 "Title": "Security patch 2",
1081 "Severity": "HIGH",
1082 "Status": "NON_COMPLIANT",
1083 },
1084 ],
1085 }),
1086 );
1087 svc.put_compliance_items(&req).unwrap();
1088
1089 let req = make_request("ListComplianceItems", json!({}));
1091 let resp = svc.list_compliance_items(&req).unwrap();
1092 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1093 assert_eq!(body["ComplianceItems"].as_array().unwrap().len(), 2);
1094
1095 let req = make_request("ListComplianceSummaries", json!({}));
1097 let resp = svc.list_compliance_summaries(&req).unwrap();
1098 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1099 assert_eq!(body["ComplianceSummaryItems"].as_array().unwrap().len(), 1);
1100
1101 let req = make_request("ListResourceComplianceSummaries", json!({}));
1103 let resp = svc.list_resource_compliance_summaries(&req).unwrap();
1104 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1105 assert_eq!(
1106 body["ResourceComplianceSummaryItems"]
1107 .as_array()
1108 .unwrap()
1109 .len(),
1110 1
1111 );
1112 }
1113
1114 fn create_mw_with_target_and_task(svc: &SsmService) -> (String, String, String) {
1117 let req = make_request(
1119 "CreateMaintenanceWindow",
1120 json!({
1121 "Name": "test-mw",
1122 "Schedule": "cron(0 2 ? * SUN *)",
1123 "Duration": 3,
1124 "Cutoff": 1,
1125 "AllowUnassociatedTargets": true,
1126 }),
1127 );
1128 let resp = svc.create_maintenance_window(&req).unwrap();
1129 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1130 let window_id = body["WindowId"].as_str().unwrap().to_string();
1131
1132 let req = make_request(
1134 "RegisterTargetWithMaintenanceWindow",
1135 json!({
1136 "WindowId": window_id,
1137 "ResourceType": "INSTANCE",
1138 "Targets": [{"Key": "InstanceIds", "Values": ["i-001"]}],
1139 "Name": "test-target",
1140 }),
1141 );
1142 let resp = svc.register_target_with_maintenance_window(&req).unwrap();
1143 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1144 let target_id = body["WindowTargetId"].as_str().unwrap().to_string();
1145
1146 let req = make_request(
1148 "RegisterTaskWithMaintenanceWindow",
1149 json!({
1150 "WindowId": window_id,
1151 "TaskArn": "AWS-RunShellScript",
1152 "TaskType": "RUN_COMMAND",
1153 "Targets": [{"Key": "WindowTargetIds", "Values": [target_id]}],
1154 "Name": "test-task",
1155 }),
1156 );
1157 let resp = svc.register_task_with_maintenance_window(&req).unwrap();
1158 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1159 let task_id = body["WindowTaskId"].as_str().unwrap().to_string();
1160
1161 (window_id, target_id, task_id)
1162 }
1163
1164 #[test]
1165 fn maintenance_window_update_target_and_task() {
1166 let svc = make_service();
1167 let (window_id, target_id, task_id) = create_mw_with_target_and_task(&svc);
1168
1169 let req = make_request(
1171 "UpdateMaintenanceWindowTarget",
1172 json!({
1173 "WindowId": window_id,
1174 "WindowTargetId": target_id,
1175 "Name": "updated-target",
1176 }),
1177 );
1178 let resp = svc.update_maintenance_window_target(&req).unwrap();
1179 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1180 assert_eq!(body["Name"].as_str().unwrap(), "updated-target");
1181
1182 let req = make_request(
1184 "GetMaintenanceWindowTask",
1185 json!({
1186 "WindowId": window_id,
1187 "WindowTaskId": task_id,
1188 }),
1189 );
1190 let resp = svc.get_maintenance_window_task(&req).unwrap();
1191 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1192 assert_eq!(body["TaskArn"].as_str().unwrap(), "AWS-RunShellScript");
1193 assert_eq!(body["Name"].as_str().unwrap(), "test-task");
1194
1195 let req = make_request(
1197 "UpdateMaintenanceWindowTask",
1198 json!({
1199 "WindowId": window_id,
1200 "WindowTaskId": task_id,
1201 "Name": "updated-task",
1202 "MaxConcurrency": "10",
1203 }),
1204 );
1205 let resp = svc.update_maintenance_window_task(&req).unwrap();
1206 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1207 assert_eq!(body["Name"].as_str().unwrap(), "updated-task");
1208 assert_eq!(body["MaxConcurrency"].as_str().unwrap(), "10");
1209 }
1210
1211 #[test]
1212 fn maintenance_window_execution_lifecycle() {
1213 let svc = make_service();
1214 let (window_id, _, _) = create_mw_with_target_and_task(&svc);
1215
1216 let exec_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
1217 let task_exec_id = "11111111-2222-3333-4444-555555555555";
1218
1219 {
1221 let now = chrono::Utc::now();
1222 let mut state = svc.state.write();
1223 let exec = crate::state::MaintenanceWindowExecution {
1224 window_execution_id: exec_id.to_string(),
1225 window_id: window_id.clone(),
1226 status: "IN_PROGRESS".to_string(),
1227 start_time: now,
1228 end_time: None,
1229 tasks: vec![crate::state::MaintenanceWindowExecutionTask {
1230 task_execution_id: task_exec_id.to_string(),
1231 window_execution_id: exec_id.to_string(),
1232 task_arn: "AWS-RunShellScript".to_string(),
1233 task_type: "RUN_COMMAND".to_string(),
1234 status: "IN_PROGRESS".to_string(),
1235 start_time: now,
1236 end_time: None,
1237 invocations: vec![crate::state::MaintenanceWindowExecutionTaskInvocation {
1238 invocation_id: "inv-001".to_string(),
1239 task_execution_id: task_exec_id.to_string(),
1240 window_execution_id: exec_id.to_string(),
1241 execution_id: Some("cmd-001".to_string()),
1242 status: "IN_PROGRESS".to_string(),
1243 start_time: now,
1244 end_time: None,
1245 parameters: None,
1246 owner_information: None,
1247 window_target_id: None,
1248 status_details: None,
1249 }],
1250 }],
1251 };
1252 state.maintenance_window_executions.push(exec);
1253 }
1254
1255 let req = make_request(
1257 "DescribeMaintenanceWindowExecutions",
1258 json!({ "WindowId": window_id }),
1259 );
1260 let resp = svc.describe_maintenance_window_executions(&req).unwrap();
1261 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1262 assert_eq!(body["WindowExecutions"].as_array().unwrap().len(), 1);
1263
1264 let req = make_request(
1266 "GetMaintenanceWindowExecution",
1267 json!({ "WindowExecutionId": exec_id }),
1268 );
1269 let resp = svc.get_maintenance_window_execution(&req).unwrap();
1270 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1271 assert_eq!(body["Status"].as_str().unwrap(), "IN_PROGRESS");
1272
1273 let req = make_request(
1275 "DescribeMaintenanceWindowExecutionTasks",
1276 json!({ "WindowExecutionId": exec_id }),
1277 );
1278 let resp = svc
1279 .describe_maintenance_window_execution_tasks(&req)
1280 .unwrap();
1281 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1282 assert_eq!(
1283 body["WindowExecutionTaskIdentities"]
1284 .as_array()
1285 .unwrap()
1286 .len(),
1287 1
1288 );
1289
1290 let req = make_request(
1292 "GetMaintenanceWindowExecutionTask",
1293 json!({
1294 "WindowExecutionId": exec_id,
1295 "TaskId": task_exec_id,
1296 }),
1297 );
1298 let resp = svc.get_maintenance_window_execution_task(&req).unwrap();
1299 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1300 assert_eq!(body["TaskArn"].as_str().unwrap(), "AWS-RunShellScript");
1301
1302 let req = make_request(
1304 "DescribeMaintenanceWindowExecutionTaskInvocations",
1305 json!({
1306 "WindowExecutionId": exec_id,
1307 "TaskId": task_exec_id,
1308 }),
1309 );
1310 let resp = svc
1311 .describe_maintenance_window_execution_task_invocations(&req)
1312 .unwrap();
1313 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1314 assert_eq!(
1315 body["WindowExecutionTaskInvocationIdentities"]
1316 .as_array()
1317 .unwrap()
1318 .len(),
1319 1
1320 );
1321
1322 let req = make_request(
1324 "GetMaintenanceWindowExecutionTaskInvocation",
1325 json!({
1326 "WindowExecutionId": exec_id,
1327 "TaskId": task_exec_id,
1328 "InvocationId": "inv-001",
1329 }),
1330 );
1331 let resp = svc
1332 .get_maintenance_window_execution_task_invocation(&req)
1333 .unwrap();
1334 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1335 assert_eq!(body["ExecutionId"].as_str().unwrap(), "cmd-001");
1336
1337 let req = make_request(
1339 "CancelMaintenanceWindowExecution",
1340 json!({ "WindowExecutionId": exec_id }),
1341 );
1342 let resp = svc.cancel_maintenance_window_execution(&req).unwrap();
1343 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1344 assert_eq!(body["WindowExecutionId"].as_str().unwrap(), exec_id);
1345
1346 let req = make_request("DescribeMaintenanceWindowSchedule", json!({}));
1348 let resp = svc.describe_maintenance_window_schedule(&req).unwrap();
1349 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1350 assert!(body["ScheduledWindowExecutions"]
1351 .as_array()
1352 .unwrap()
1353 .is_empty());
1354
1355 let req = make_request(
1357 "DescribeMaintenanceWindowsForTarget",
1358 json!({
1359 "ResourceType": "INSTANCE",
1360 "Targets": [{"Key": "InstanceIds", "Values": ["i-001"]}],
1361 }),
1362 );
1363 let resp = svc.describe_maintenance_windows_for_target(&req).unwrap();
1364 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1365 assert_eq!(body["WindowIdentities"].as_array().unwrap().len(), 1);
1366 }
1367
1368 #[test]
1371 fn update_patch_baseline_works() {
1372 let svc = make_service();
1373
1374 let req = make_request(
1376 "CreatePatchBaseline",
1377 json!({
1378 "Name": "test-baseline",
1379 "OperatingSystem": "AMAZON_LINUX_2",
1380 "Description": "original description",
1381 }),
1382 );
1383 let resp = svc.create_patch_baseline(&req).unwrap();
1384 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1385 let baseline_id = body["BaselineId"].as_str().unwrap().to_string();
1386
1387 let req = make_request(
1389 "UpdatePatchBaseline",
1390 json!({
1391 "BaselineId": baseline_id,
1392 "Name": "updated-baseline",
1393 "Description": "updated description",
1394 "ApprovedPatches": ["KB001", "KB002"],
1395 }),
1396 );
1397 let resp = svc.update_patch_baseline(&req).unwrap();
1398 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1399 assert_eq!(body["Name"].as_str().unwrap(), "updated-baseline");
1400 assert_eq!(body["Description"].as_str().unwrap(), "updated description");
1401 assert_eq!(body["ApprovedPatches"].as_array().unwrap().len(), 2);
1402 }
1403
1404 #[test]
1407 fn resource_data_sync_lifecycle() {
1408 let svc = make_service();
1409
1410 let req = make_request(
1412 "CreateResourceDataSync",
1413 json!({
1414 "SyncName": "test-sync",
1415 "SyncType": "SyncFromSource",
1416 "SyncSource": {
1417 "SourceType": "AWS",
1418 "SourceRegions": ["us-east-1"],
1419 },
1420 }),
1421 );
1422 svc.create_resource_data_sync(&req).unwrap();
1423
1424 let req = make_request("ListResourceDataSync", json!({}));
1426 let resp = svc.list_resource_data_sync(&req).unwrap();
1427 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1428 assert_eq!(body["ResourceDataSyncItems"].as_array().unwrap().len(), 1);
1429 assert_eq!(
1430 body["ResourceDataSyncItems"][0]["SyncName"]
1431 .as_str()
1432 .unwrap(),
1433 "test-sync"
1434 );
1435
1436 let req = make_request(
1438 "UpdateResourceDataSync",
1439 json!({
1440 "SyncName": "test-sync",
1441 "SyncType": "SyncFromSource",
1442 "SyncSource": {
1443 "SourceType": "AWS",
1444 "SourceRegions": ["us-east-1", "us-west-2"],
1445 },
1446 }),
1447 );
1448 svc.update_resource_data_sync(&req).unwrap();
1449
1450 let req = make_request("DeleteResourceDataSync", json!({ "SyncName": "test-sync" }));
1452 svc.delete_resource_data_sync(&req).unwrap();
1453
1454 let req = make_request("ListResourceDataSync", json!({}));
1456 let resp = svc.list_resource_data_sync(&req).unwrap();
1457 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1458 assert!(body["ResourceDataSyncItems"].as_array().unwrap().is_empty());
1459 }
1460
1461 #[test]
1464 fn describe_instance_patch_states_returns_empty() {
1465 let svc = make_service();
1466 let req = make_request(
1467 "DescribeInstancePatchStates",
1468 json!({ "InstanceIds": ["i-001"] }),
1469 );
1470 let resp = svc.describe_instance_patch_states(&req).unwrap();
1471 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1472 assert!(body["InstancePatchStates"].as_array().unwrap().is_empty());
1473 }
1474
1475 #[test]
1476 fn describe_instance_patches_returns_empty() {
1477 let svc = make_service();
1478 let req = make_request("DescribeInstancePatches", json!({ "InstanceId": "i-001" }));
1479 let resp = svc.describe_instance_patches(&req).unwrap();
1480 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1481 assert!(body["Patches"].as_array().unwrap().is_empty());
1482 }
1483
1484 #[test]
1485 fn describe_effective_patches_returns_empty() {
1486 let svc = make_service();
1487 let req = make_request(
1488 "DescribeEffectivePatchesForPatchBaseline",
1489 json!({ "BaselineId": "pb-12345678901234567" }),
1490 );
1491 let resp = svc
1492 .describe_effective_patches_for_patch_baseline(&req)
1493 .unwrap();
1494 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1495 assert!(body["EffectivePatches"].as_array().unwrap().is_empty());
1496 }
1497
1498 #[test]
1499 fn get_ops_summary_returns_empty() {
1500 let svc = make_service();
1501 let req = make_request("GetOpsSummary", json!({}));
1502 let resp = svc.get_ops_summary(&req).unwrap();
1503 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1504 assert!(body["Entities"].as_array().unwrap().is_empty());
1505 }
1506
1507 #[test]
1510 fn associate_and_disassociate_ops_item_related_item() {
1511 let svc = make_service();
1512 let req = make_request(
1514 "CreateOpsItem",
1515 json!({ "Title": "Test", "Source": "test", "Description": "test desc" }),
1516 );
1517 let resp = svc.create_ops_item(&req).unwrap();
1518 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1519 let ops_item_id = body["OpsItemId"].as_str().unwrap().to_string();
1520
1521 let req = make_request(
1523 "AssociateOpsItemRelatedItem",
1524 json!({
1525 "OpsItemId": ops_item_id,
1526 "AssociationType": "IsParentOf",
1527 "ResourceType": "AWS::SSMIncidents::IncidentRecord",
1528 "ResourceUri": "arn:aws:ssm-incidents::123456789012:incident-record/test"
1529 }),
1530 );
1531 let resp = svc.associate_ops_item_related_item(&req).unwrap();
1532 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1533 let assoc_id = body["AssociationId"].as_str().unwrap().to_string();
1534
1535 let req = make_request(
1537 "ListOpsItemRelatedItems",
1538 json!({ "OpsItemId": ops_item_id }),
1539 );
1540 let resp = svc.list_ops_item_related_items(&req).unwrap();
1541 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1542 assert_eq!(body["Summaries"].as_array().unwrap().len(), 1);
1543
1544 let req = make_request(
1546 "DisassociateOpsItemRelatedItem",
1547 json!({ "OpsItemId": ops_item_id, "AssociationId": assoc_id }),
1548 );
1549 svc.disassociate_ops_item_related_item(&req).unwrap();
1550 }
1551
1552 #[test]
1553 fn list_ops_item_events_returns_empty() {
1554 let svc = make_service();
1555 let req = make_request("ListOpsItemEvents", json!({}));
1556 let resp = svc.list_ops_item_events(&req).unwrap();
1557 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1558 assert!(body["Summaries"].as_array().unwrap().is_empty());
1559 }
1560
1561 #[test]
1564 fn ops_metadata_lifecycle() {
1565 let svc = make_service();
1566
1567 let req = make_request(
1569 "CreateOpsMetadata",
1570 json!({
1571 "ResourceId": "test-resource",
1572 "Metadata": { "key1": { "Value": "val1" } }
1573 }),
1574 );
1575 let resp = svc.create_ops_metadata(&req).unwrap();
1576 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1577 let arn = body["OpsMetadataArn"].as_str().unwrap().to_string();
1578
1579 let req = make_request("GetOpsMetadata", json!({ "OpsMetadataArn": arn }));
1581 let resp = svc.get_ops_metadata(&req).unwrap();
1582 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1583 assert_eq!(body["ResourceId"].as_str().unwrap(), "test-resource");
1584
1585 let req = make_request(
1587 "UpdateOpsMetadata",
1588 json!({
1589 "OpsMetadataArn": arn,
1590 "MetadataToUpdate": { "key2": { "Value": "val2" } }
1591 }),
1592 );
1593 svc.update_ops_metadata(&req).unwrap();
1594
1595 let req = make_request("ListOpsMetadata", json!({}));
1597 let resp = svc.list_ops_metadata(&req).unwrap();
1598 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1599 assert_eq!(body["OpsMetadataList"].as_array().unwrap().len(), 1);
1600
1601 let req = make_request("DeleteOpsMetadata", json!({ "OpsMetadataArn": arn }));
1603 svc.delete_ops_metadata(&req).unwrap();
1604 }
1605
1606 #[test]
1609 fn automation_execution_lifecycle() {
1610 let svc = make_service();
1611
1612 let req = make_request(
1614 "StartAutomationExecution",
1615 json!({ "DocumentName": "AWS-RunShellScript" }),
1616 );
1617 let resp = svc.start_automation_execution(&req).unwrap();
1618 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1619 let exec_id = body["AutomationExecutionId"].as_str().unwrap().to_string();
1620
1621 let req = make_request(
1623 "GetAutomationExecution",
1624 json!({ "AutomationExecutionId": exec_id }),
1625 );
1626 let resp = svc.get_automation_execution(&req).unwrap();
1627 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1628 assert_eq!(
1629 body["AutomationExecution"]["AutomationExecutionStatus"]
1630 .as_str()
1631 .unwrap(),
1632 "InProgress"
1633 );
1634
1635 let req = make_request("DescribeAutomationExecutions", json!({}));
1637 let resp = svc.describe_automation_executions(&req).unwrap();
1638 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1639 assert_eq!(
1640 body["AutomationExecutionMetadataList"]
1641 .as_array()
1642 .unwrap()
1643 .len(),
1644 1
1645 );
1646
1647 let req = make_request(
1649 "DescribeAutomationStepExecutions",
1650 json!({ "AutomationExecutionId": exec_id }),
1651 );
1652 let resp = svc.describe_automation_step_executions(&req).unwrap();
1653 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1654 assert!(body["StepExecutions"].as_array().unwrap().is_empty());
1655
1656 let req = make_request(
1658 "SendAutomationSignal",
1659 json!({ "AutomationExecutionId": exec_id, "SignalType": "Approve" }),
1660 );
1661 svc.send_automation_signal(&req).unwrap();
1662
1663 let req = make_request(
1665 "StopAutomationExecution",
1666 json!({ "AutomationExecutionId": exec_id }),
1667 );
1668 svc.stop_automation_execution(&req).unwrap();
1669 }
1670
1671 #[test]
1672 fn start_change_request_execution_works() {
1673 let svc = make_service();
1674 let req = make_request(
1675 "StartChangeRequestExecution",
1676 json!({
1677 "DocumentName": "AWS-ChangeManager",
1678 "Runbooks": [{ "DocumentName": "AWS-RunShellScript" }]
1679 }),
1680 );
1681 let resp = svc.start_change_request_execution(&req).unwrap();
1682 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1683 assert!(body["AutomationExecutionId"].as_str().is_some());
1684 }
1685
1686 #[test]
1687 fn execution_preview_lifecycle() {
1688 let svc = make_service();
1689
1690 let req = make_request(
1691 "StartExecutionPreview",
1692 json!({ "DocumentName": "AWS-RunShellScript" }),
1693 );
1694 let resp = svc.start_execution_preview(&req).unwrap();
1695 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1696 let preview_id = body["ExecutionPreviewId"].as_str().unwrap().to_string();
1697
1698 let req = make_request(
1699 "GetExecutionPreview",
1700 json!({ "ExecutionPreviewId": preview_id }),
1701 );
1702 let resp = svc.get_execution_preview(&req).unwrap();
1703 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1704 assert_eq!(body["Status"].as_str().unwrap(), "Success");
1705 }
1706
1707 #[test]
1710 fn session_lifecycle() {
1711 let svc = make_service();
1712
1713 let req = make_request("StartSession", json!({ "Target": "i-001" }));
1715 let resp = svc.start_session(&req).unwrap();
1716 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1717 let session_id = body["SessionId"].as_str().unwrap().to_string();
1718 assert!(body["TokenValue"].as_str().is_some());
1719
1720 let req = make_request("ResumeSession", json!({ "SessionId": session_id }));
1722 let resp = svc.resume_session(&req).unwrap();
1723 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1724 assert_eq!(body["SessionId"].as_str().unwrap(), session_id);
1725
1726 let req = make_request("DescribeSessions", json!({ "State": "Active" }));
1728 let resp = svc.describe_sessions(&req).unwrap();
1729 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1730 assert_eq!(body["Sessions"].as_array().unwrap().len(), 1);
1731
1732 let req = make_request("TerminateSession", json!({ "SessionId": session_id }));
1734 svc.terminate_session(&req).unwrap();
1735
1736 let req = make_request("DescribeSessions", json!({ "State": "History" }));
1738 let resp = svc.describe_sessions(&req).unwrap();
1739 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1740 assert_eq!(body["Sessions"].as_array().unwrap().len(), 1);
1741 }
1742
1743 #[test]
1744 fn start_access_request_and_get_token() {
1745 let svc = make_service();
1746
1747 let req = make_request(
1748 "StartAccessRequest",
1749 json!({ "Reason": "test", "Targets": [{"Key": "InstanceIds", "Values": ["i-001"]}] }),
1750 );
1751 let resp = svc.start_access_request(&req).unwrap();
1752 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1753 let ar_id = body["AccessRequestId"].as_str().unwrap().to_string();
1754
1755 let req = make_request("GetAccessToken", json!({ "AccessRequestId": ar_id }));
1756 let resp = svc.get_access_token(&req).unwrap();
1757 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1758 assert!(body["Credentials"]["AccessKeyId"].as_str().is_some());
1759 assert_eq!(body["AccessRequestStatus"].as_str(), Some("Approved"));
1760 }
1761
1762 #[test]
1765 fn activation_lifecycle() {
1766 let svc = make_service();
1767
1768 let req = make_request(
1770 "CreateActivation",
1771 json!({ "IamRole": "SSMServiceRole", "Description": "test" }),
1772 );
1773 let resp = svc.create_activation(&req).unwrap();
1774 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1775 let activation_id = body["ActivationId"].as_str().unwrap().to_string();
1776 assert!(body["ActivationCode"].as_str().is_some());
1777
1778 let req = make_request("DescribeActivations", json!({}));
1780 let resp = svc.describe_activations(&req).unwrap();
1781 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1782 assert_eq!(body["ActivationList"].as_array().unwrap().len(), 1);
1783
1784 let req = make_request("DeleteActivation", json!({ "ActivationId": activation_id }));
1786 svc.delete_activation(&req).unwrap();
1787 }
1788
1789 #[test]
1790 fn deregister_managed_instance_no_error() {
1791 let svc = make_service();
1792 let req = make_request(
1793 "DeregisterManagedInstance",
1794 json!({ "InstanceId": "mi-01234567890123456" }),
1795 );
1796 svc.deregister_managed_instance(&req).unwrap();
1797 }
1798
1799 #[test]
1800 fn describe_instance_information_empty() {
1801 let svc = make_service();
1802 let req = make_request("DescribeInstanceInformation", json!({}));
1803 let resp = svc.describe_instance_information(&req).unwrap();
1804 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1805 assert!(body["InstanceInformationList"]
1806 .as_array()
1807 .unwrap()
1808 .is_empty());
1809 }
1810
1811 #[test]
1812 fn describe_instance_properties_empty() {
1813 let svc = make_service();
1814 let req = make_request("DescribeInstanceProperties", json!({}));
1815 let resp = svc.describe_instance_properties(&req).unwrap();
1816 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1817 assert!(body["InstanceProperties"].as_array().unwrap().is_empty());
1818 }
1819
1820 #[test]
1823 fn list_nodes_returns_empty() {
1824 let svc = make_service();
1825 let req = make_request("ListNodes", json!({}));
1826 let resp = svc.list_nodes(&req).unwrap();
1827 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1828 assert!(body["Nodes"].as_array().unwrap().is_empty());
1829 }
1830
1831 #[test]
1832 fn list_nodes_summary_returns_empty() {
1833 let svc = make_service();
1834 let req = make_request(
1835 "ListNodesSummary",
1836 json!({ "Aggregators": [{"AggregatorType": "Count"}] }),
1837 );
1838 let resp = svc.list_nodes_summary(&req).unwrap();
1839 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1840 assert!(body["Summary"].as_array().unwrap().is_empty());
1841 }
1842
1843 #[test]
1844 fn describe_effective_instance_associations_empty() {
1845 let svc = make_service();
1846 let req = make_request(
1847 "DescribeEffectiveInstanceAssociations",
1848 json!({ "InstanceId": "i-001" }),
1849 );
1850 let resp = svc.describe_effective_instance_associations(&req).unwrap();
1851 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1852 assert!(body["Associations"].as_array().unwrap().is_empty());
1853 }
1854
1855 #[test]
1856 fn describe_instance_associations_status_empty() {
1857 let svc = make_service();
1858 let req = make_request(
1859 "DescribeInstanceAssociationsStatus",
1860 json!({ "InstanceId": "i-001" }),
1861 );
1862 let resp = svc.describe_instance_associations_status(&req).unwrap();
1863 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1864 assert!(body["InstanceAssociationStatusInfos"]
1865 .as_array()
1866 .unwrap()
1867 .is_empty());
1868 }
1869
1870 fn put_param(svc: &SsmService, name: &str, value: &str) -> i64 {
1873 let req = make_request(
1874 "PutParameter",
1875 json!({
1876 "Name": name,
1877 "Value": value,
1878 "Type": "String",
1879 "Overwrite": true,
1880 }),
1881 );
1882 let resp = svc.put_parameter(&req).unwrap();
1883 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1884 body["Version"].as_i64().unwrap()
1885 }
1886
1887 #[test]
1888 fn label_and_unlabel_parameter_version() {
1889 let svc = make_service();
1890
1891 put_param(&svc, "/label/test", "v1");
1893 put_param(&svc, "/label/test", "v2");
1894
1895 let req = make_request(
1897 "LabelParameterVersion",
1898 json!({
1899 "Name": "/label/test",
1900 "ParameterVersion": 1,
1901 "Labels": ["prod", "stable"],
1902 }),
1903 );
1904 let resp = svc.label_parameter_version(&req).unwrap();
1905 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1906 assert!(body["InvalidLabels"].as_array().unwrap().is_empty());
1907 assert_eq!(body["ParameterVersion"].as_i64().unwrap(), 1);
1908
1909 let req = make_request("GetParameterHistory", json!({ "Name": "/label/test" }));
1911 let resp = svc.get_parameter_history(&req).unwrap();
1912 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1913 let params = body["Parameters"].as_array().unwrap();
1914 let v1 = params
1915 .iter()
1916 .find(|p| p["Version"].as_i64() == Some(1))
1917 .unwrap();
1918 let labels = v1["Labels"].as_array().unwrap();
1919 assert!(labels.iter().any(|l| l.as_str() == Some("prod")));
1920 assert!(labels.iter().any(|l| l.as_str() == Some("stable")));
1921
1922 let req = make_request(
1924 "UnlabelParameterVersion",
1925 json!({
1926 "Name": "/label/test",
1927 "ParameterVersion": 1,
1928 "Labels": ["prod"],
1929 }),
1930 );
1931 let resp = svc.unlabel_parameter_version(&req).unwrap();
1932 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1933 assert!(body["InvalidLabels"].as_array().unwrap().is_empty());
1934 let removed = body["RemovedLabels"].as_array().unwrap();
1935 assert_eq!(removed.len(), 1);
1936 assert_eq!(removed[0].as_str().unwrap(), "prod");
1937 }
1938
1939 #[test]
1940 fn label_parameter_version_defaults_to_latest() {
1941 let svc = make_service();
1942 put_param(&svc, "/label/default", "v1");
1943 put_param(&svc, "/label/default", "v2");
1944
1945 let req = make_request(
1947 "LabelParameterVersion",
1948 json!({
1949 "Name": "/label/default",
1950 "Labels": ["latest-label"],
1951 }),
1952 );
1953 let resp = svc.label_parameter_version(&req).unwrap();
1954 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1955 assert_eq!(body["ParameterVersion"].as_i64().unwrap(), 2);
1956 }
1957
1958 #[test]
1959 fn label_parameter_version_invalid_labels() {
1960 let svc = make_service();
1961 put_param(&svc, "/label/invalid", "v1");
1962
1963 let req = make_request(
1965 "LabelParameterVersion",
1966 json!({
1967 "Name": "/label/invalid",
1968 "Labels": ["aws-reserved", "valid-label"],
1969 }),
1970 );
1971 let resp = svc.label_parameter_version(&req).unwrap();
1972 let body: Value = serde_json::from_slice(&resp.body).unwrap();
1973 let invalid = body["InvalidLabels"].as_array().unwrap();
1974 assert_eq!(invalid.len(), 1);
1975 assert_eq!(invalid[0].as_str().unwrap(), "aws-reserved");
1976 }
1977
1978 #[test]
1979 fn label_parameter_version_not_found() {
1980 let svc = make_service();
1981 put_param(&svc, "/label/notfound", "v1");
1982
1983 let req = make_request(
1984 "LabelParameterVersion",
1985 json!({
1986 "Name": "/label/notfound",
1987 "ParameterVersion": 999,
1988 "Labels": ["test"],
1989 }),
1990 );
1991 let result = svc.label_parameter_version(&req);
1992 assert!(result.is_err());
1993 }
1994
1995 #[test]
1996 fn unlabel_parameter_version_returns_invalid_for_missing_labels() {
1997 let svc = make_service();
1998 put_param(&svc, "/label/missing", "v1");
1999
2000 let req = make_request(
2001 "UnlabelParameterVersion",
2002 json!({
2003 "Name": "/label/missing",
2004 "ParameterVersion": 1,
2005 "Labels": ["nonexistent"],
2006 }),
2007 );
2008 let resp = svc.unlabel_parameter_version(&req).unwrap();
2009 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2010 let invalid = body["InvalidLabels"].as_array().unwrap();
2011 assert_eq!(invalid.len(), 1);
2012 assert_eq!(invalid[0].as_str().unwrap(), "nonexistent");
2013 }
2014
2015 #[test]
2016 fn unlabel_parameter_version_requires_parameter_version() {
2017 let svc = make_service();
2018 put_param(&svc, "/label/reqver", "v1");
2019
2020 let req = make_request(
2022 "UnlabelParameterVersion",
2023 json!({
2024 "Name": "/label/reqver",
2025 "Labels": ["some-label"],
2026 }),
2027 );
2028 let result = svc.unlabel_parameter_version(&req);
2029 assert!(result.is_err());
2030 }
2031
2032 fn create_doc(svc: &SsmService, name: &str) {
2035 let req = make_request(
2036 "CreateDocument",
2037 json!({
2038 "Name": name,
2039 "Content": r#"{"schemaVersion":"2.2","mainSteps":[]}"#,
2040 "DocumentType": "Command",
2041 }),
2042 );
2043 svc.create_document(&req).unwrap();
2044 }
2045
2046 #[test]
2047 fn update_document_and_default_version() {
2048 let svc = make_service();
2049 create_doc(&svc, "TestDoc");
2050
2051 let req = make_request(
2053 "UpdateDocument",
2054 json!({
2055 "Name": "TestDoc",
2056 "Content": r#"{"schemaVersion":"2.2","description":"v2","mainSteps":[]}"#,
2057 "VersionName": "release-2",
2058 }),
2059 );
2060 let resp = svc.update_document(&req).unwrap();
2061 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2062 let desc = &body["DocumentDescription"];
2063 assert_eq!(desc["DocumentVersion"].as_str().unwrap(), "2");
2064 assert_eq!(desc["VersionName"].as_str().unwrap(), "release-2");
2065
2066 let req = make_request("ListDocumentVersions", json!({ "Name": "TestDoc" }));
2068 let resp = svc.list_document_versions(&req).unwrap();
2069 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2070 assert_eq!(body["DocumentVersions"].as_array().unwrap().len(), 2);
2071
2072 let req = make_request(
2074 "UpdateDocumentDefaultVersion",
2075 json!({
2076 "Name": "TestDoc",
2077 "DocumentVersion": "2",
2078 }),
2079 );
2080 let resp = svc.update_document_default_version(&req).unwrap();
2081 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2082 assert_eq!(body["Description"]["DefaultVersion"].as_str().unwrap(), "2");
2083
2084 let req = make_request("DescribeDocument", json!({ "Name": "TestDoc" }));
2086 let resp = svc.describe_document(&req).unwrap();
2087 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2088 assert_eq!(body["Document"]["DefaultVersion"].as_str().unwrap(), "2");
2089 }
2090
2091 #[test]
2092 fn update_document_duplicate_content_fails() {
2093 let svc = make_service();
2094 create_doc(&svc, "DupDoc");
2095
2096 let req = make_request(
2098 "UpdateDocument",
2099 json!({
2100 "Name": "DupDoc",
2101 "Content": r#"{"schemaVersion":"2.2","mainSteps":[]}"#,
2102 }),
2103 );
2104 let result = svc.update_document(&req);
2105 assert!(result.is_err());
2106 }
2107
2108 #[test]
2109 fn document_permissions_modify_and_describe() {
2110 let svc = make_service();
2111 create_doc(&svc, "PermDoc");
2112
2113 let req = make_request(
2115 "ModifyDocumentPermission",
2116 json!({
2117 "Name": "PermDoc",
2118 "PermissionType": "Share",
2119 "AccountIdsToAdd": ["111111111111", "222222222222"],
2120 }),
2121 );
2122 svc.modify_document_permission(&req).unwrap();
2123
2124 let req = make_request(
2126 "DescribeDocumentPermission",
2127 json!({
2128 "Name": "PermDoc",
2129 "PermissionType": "Share",
2130 }),
2131 );
2132 let resp = svc.describe_document_permission(&req).unwrap();
2133 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2134 let ids = body["AccountIds"].as_array().unwrap();
2135 assert_eq!(ids.len(), 2);
2136
2137 let req = make_request(
2139 "ModifyDocumentPermission",
2140 json!({
2141 "Name": "PermDoc",
2142 "PermissionType": "Share",
2143 "AccountIdsToRemove": ["111111111111"],
2144 }),
2145 );
2146 svc.modify_document_permission(&req).unwrap();
2147
2148 let req = make_request(
2150 "DescribeDocumentPermission",
2151 json!({
2152 "Name": "PermDoc",
2153 "PermissionType": "Share",
2154 }),
2155 );
2156 let resp = svc.describe_document_permission(&req).unwrap();
2157 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2158 let ids = body["AccountIds"].as_array().unwrap();
2159 assert_eq!(ids.len(), 1);
2160 assert_eq!(ids[0].as_str().unwrap(), "222222222222");
2161 }
2162
2163 #[test]
2164 fn modify_document_permission_invalid_type() {
2165 let svc = make_service();
2166 create_doc(&svc, "PermDoc2");
2167
2168 let req = make_request(
2169 "ModifyDocumentPermission",
2170 json!({
2171 "Name": "PermDoc2",
2172 "PermissionType": "Invalid",
2173 "AccountIdsToAdd": ["111111111111"],
2174 }),
2175 );
2176 let result = svc.modify_document_permission(&req);
2177 assert!(result.is_err());
2178 }
2179
2180 #[test]
2183 fn describe_maintenance_window_targets_and_tasks() {
2184 let svc = make_service();
2185 let (window_id, _target_id, _task_id) = create_mw_with_target_and_task(&svc);
2186
2187 let req = make_request(
2189 "DescribeMaintenanceWindowTargets",
2190 json!({ "WindowId": window_id }),
2191 );
2192 let resp = svc.describe_maintenance_window_targets(&req).unwrap();
2193 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2194 let targets = body["Targets"].as_array().unwrap();
2195 assert_eq!(targets.len(), 1);
2196 assert_eq!(targets[0]["Name"].as_str().unwrap(), "test-target");
2197
2198 let req = make_request(
2200 "DescribeMaintenanceWindowTasks",
2201 json!({ "WindowId": window_id }),
2202 );
2203 let resp = svc.describe_maintenance_window_tasks(&req).unwrap();
2204 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2205 let tasks = body["Tasks"].as_array().unwrap();
2206 assert_eq!(tasks.len(), 1);
2207 assert_eq!(tasks[0]["TaskArn"].as_str().unwrap(), "AWS-RunShellScript");
2208 assert_eq!(tasks[0]["Name"].as_str().unwrap(), "test-task");
2209 }
2210
2211 fn create_baseline(svc: &SsmService, name: &str) -> String {
2214 let req = make_request(
2215 "CreatePatchBaseline",
2216 json!({
2217 "Name": name,
2218 "OperatingSystem": "AMAZON_LINUX_2",
2219 "Description": "test baseline",
2220 }),
2221 );
2222 let resp = svc.create_patch_baseline(&req).unwrap();
2223 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2224 body["BaselineId"].as_str().unwrap().to_string()
2225 }
2226
2227 #[test]
2228 fn patch_baseline_get_and_delete() {
2229 let svc = make_service();
2230 let baseline_id = create_baseline(&svc, "get-del-baseline");
2231
2232 let req = make_request("GetPatchBaseline", json!({ "BaselineId": baseline_id }));
2234 let resp = svc.get_patch_baseline(&req).unwrap();
2235 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2236 assert_eq!(body["Name"].as_str().unwrap(), "get-del-baseline");
2237 assert_eq!(body["OperatingSystem"].as_str().unwrap(), "AMAZON_LINUX_2");
2238 assert_eq!(body["Description"].as_str().unwrap(), "test baseline");
2239
2240 let req = make_request("DeletePatchBaseline", json!({ "BaselineId": baseline_id }));
2242 svc.delete_patch_baseline(&req).unwrap();
2243
2244 let req = make_request("GetPatchBaseline", json!({ "BaselineId": baseline_id }));
2246 let result = svc.get_patch_baseline(&req);
2247 assert!(result.is_err());
2248 }
2249
2250 #[test]
2251 fn describe_patch_baselines_with_filter() {
2252 let svc = make_service();
2253 create_baseline(&svc, "alpha-baseline");
2254 create_baseline(&svc, "beta-baseline");
2255
2256 let req = make_request(
2258 "DescribePatchBaselines",
2259 json!({
2260 "Filters": [{"Key": "NAME_PREFIX", "Values": ["alpha"]}],
2261 }),
2262 );
2263 let resp = svc.describe_patch_baselines(&req).unwrap();
2264 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2265 let baselines = body["BaselineIdentities"].as_array().unwrap();
2266 assert_eq!(baselines.len(), 1);
2267 assert_eq!(
2268 baselines[0]["BaselineName"].as_str().unwrap(),
2269 "alpha-baseline"
2270 );
2271 }
2272
2273 #[test]
2274 fn patch_group_register_and_deregister() {
2275 let svc = make_service();
2276 let baseline_id = create_baseline(&svc, "pg-baseline");
2277
2278 let req = make_request(
2280 "RegisterPatchBaselineForPatchGroup",
2281 json!({
2282 "BaselineId": baseline_id,
2283 "PatchGroup": "production",
2284 }),
2285 );
2286 let resp = svc.register_patch_baseline_for_patch_group(&req).unwrap();
2287 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2288 assert_eq!(body["PatchGroup"].as_str().unwrap(), "production");
2289
2290 let req = make_request(
2292 "GetPatchBaselineForPatchGroup",
2293 json!({
2294 "PatchGroup": "production",
2295 "OperatingSystem": "AMAZON_LINUX_2",
2296 }),
2297 );
2298 let resp = svc.get_patch_baseline_for_patch_group(&req).unwrap();
2299 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2300 assert_eq!(body["BaselineId"].as_str().unwrap(), baseline_id);
2301
2302 let req = make_request("DescribePatchGroups", json!({}));
2304 let resp = svc.describe_patch_groups(&req).unwrap();
2305 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2306 let mappings = body["Mappings"].as_array().unwrap();
2307 assert_eq!(mappings.len(), 1);
2308 assert_eq!(mappings[0]["PatchGroup"].as_str().unwrap(), "production");
2309
2310 let req = make_request(
2312 "DeregisterPatchBaselineForPatchGroup",
2313 json!({
2314 "BaselineId": baseline_id,
2315 "PatchGroup": "production",
2316 }),
2317 );
2318 svc.deregister_patch_baseline_for_patch_group(&req).unwrap();
2319
2320 let req = make_request("DescribePatchGroups", json!({}));
2322 let resp = svc.describe_patch_groups(&req).unwrap();
2323 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2324 assert!(body["Mappings"].as_array().unwrap().is_empty());
2325 }
2326
2327 #[test]
2328 fn delete_patch_baseline_removes_patch_groups() {
2329 let svc = make_service();
2330 let baseline_id = create_baseline(&svc, "del-pg-baseline");
2331
2332 let req = make_request(
2334 "RegisterPatchBaselineForPatchGroup",
2335 json!({
2336 "BaselineId": baseline_id,
2337 "PatchGroup": "staging",
2338 }),
2339 );
2340 svc.register_patch_baseline_for_patch_group(&req).unwrap();
2341
2342 let req = make_request("DeletePatchBaseline", json!({ "BaselineId": baseline_id }));
2344 svc.delete_patch_baseline(&req).unwrap();
2345
2346 let req = make_request("DescribePatchGroups", json!({}));
2348 let resp = svc.describe_patch_groups(&req).unwrap();
2349 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2350 assert!(body["Mappings"].as_array().unwrap().is_empty());
2351 }
2352
2353 #[test]
2356 fn get_command_invocation_success() {
2357 let svc = make_service();
2358 let cmd_id = send_command(&svc, "AWS-RunShellScript");
2359
2360 let req = make_request(
2361 "GetCommandInvocation",
2362 json!({
2363 "CommandId": cmd_id,
2364 "InstanceId": "i-1234567890abcdef0",
2365 }),
2366 );
2367 let resp = svc.get_command_invocation(&req).unwrap();
2368 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2369 assert_eq!(body["CommandId"].as_str().unwrap(), cmd_id);
2370 assert_eq!(body["InstanceId"].as_str().unwrap(), "i-1234567890abcdef0");
2371 assert_eq!(body["Status"].as_str().unwrap(), "Success");
2372 }
2373
2374 #[test]
2375 fn get_command_invocation_wrong_instance_fails() {
2376 let svc = make_service();
2377 let cmd_id = send_command(&svc, "AWS-RunShellScript");
2378
2379 let req = make_request(
2380 "GetCommandInvocation",
2381 json!({
2382 "CommandId": cmd_id,
2383 "InstanceId": "i-0000000000000000f",
2384 }),
2385 );
2386 let result = svc.get_command_invocation(&req);
2387 assert!(result.is_err());
2388 }
2389
2390 #[test]
2391 fn list_command_invocations() {
2392 let svc = make_service();
2393 let cmd_id = send_command(&svc, "AWS-RunShellScript");
2394
2395 let req = make_request("ListCommandInvocations", json!({}));
2397 let resp = svc.list_command_invocations(&req).unwrap();
2398 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2399 let invocations = body["CommandInvocations"].as_array().unwrap();
2400 assert!(!invocations.is_empty());
2401 assert_eq!(invocations[0]["CommandId"].as_str().unwrap(), cmd_id);
2402 assert_eq!(
2403 invocations[0]["InstanceId"].as_str().unwrap(),
2404 "i-1234567890abcdef0"
2405 );
2406
2407 let req = make_request("ListCommandInvocations", json!({ "CommandId": cmd_id }));
2409 let resp = svc.list_command_invocations(&req).unwrap();
2410 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2411 assert_eq!(body["CommandInvocations"].as_array().unwrap().len(), 1);
2412 }
2413}