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 principal: None,
464 }
465 }
466
467 fn send_command(svc: &SsmService, doc_name: &str) -> String {
468 let req = make_request(
469 "SendCommand",
470 json!({
471 "DocumentName": doc_name,
472 "InstanceIds": ["i-1234567890abcdef0"]
473 }),
474 );
475 let resp = svc.send_command(&req).unwrap();
476 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
477 body["Command"]["CommandId"].as_str().unwrap().to_string()
478 }
479
480 #[test]
481 fn list_commands_pagination() {
482 let svc = make_service();
483
484 let mut command_ids = Vec::new();
486 for i in 0..3 {
487 command_ids.push(send_command(&svc, &format!("AWS-RunShellScript-{i}")));
488 }
489
490 let req = make_request("ListCommands", json!({ "MaxResults": 1 }));
492 let resp = svc.list_commands(&req).unwrap();
493 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
494 assert_eq!(body["Commands"].as_array().unwrap().len(), 1);
495 let token = body["NextToken"].as_str().unwrap();
496
497 let req = make_request(
499 "ListCommands",
500 json!({ "MaxResults": 1, "NextToken": token }),
501 );
502 let resp = svc.list_commands(&req).unwrap();
503 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
504 assert_eq!(body["Commands"].as_array().unwrap().len(), 1);
505 let token = body["NextToken"].as_str().unwrap();
506
507 let req = make_request(
509 "ListCommands",
510 json!({ "MaxResults": 1, "NextToken": token }),
511 );
512 let resp = svc.list_commands(&req).unwrap();
513 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
514 assert_eq!(body["Commands"].as_array().unwrap().len(), 1);
515 assert!(body.get("NextToken").is_none() || body["NextToken"].is_null());
516 }
517
518 #[test]
519 fn send_command_response_omits_non_shape_fields() {
520 let svc = make_service();
521
522 let req = make_request(
524 "CreateDocument",
525 json!({
526 "Name": "TestDoc",
527 "Content": "{\"schemaVersion\":\"2.2\",\"mainSteps\":[]}",
528 "DocumentType": "Command"
529 }),
530 );
531 svc.create_document(&req).unwrap();
532
533 let req = make_request(
534 "SendCommand",
535 json!({
536 "DocumentName": "TestDoc",
537 "InstanceIds": ["i-1234567890abcdef0"],
538 "DocumentHash": "abc123hash",
539 "DocumentHashType": "Sha256",
540 "ServiceRoleArn": "arn:aws:iam::123456789012:role/MyRole"
541 }),
542 );
543 let resp = svc.send_command(&req).unwrap();
544 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
545 let cmd = &body["Command"];
546
547 assert!(
549 !cmd.as_object().unwrap().contains_key("DocumentHash"),
550 "DocumentHash should not be in SendCommand response"
551 );
552 assert!(
553 !cmd.as_object().unwrap().contains_key("DocumentHashType"),
554 "DocumentHashType should not be in SendCommand response"
555 );
556 assert!(
557 !cmd.as_object().unwrap().contains_key("ServiceRoleArn"),
558 "ServiceRoleArn should not be in SendCommand response"
559 );
560
561 assert!(cmd["CommandId"].is_string());
563 assert_eq!(cmd["DocumentName"].as_str().unwrap(), "TestDoc");
564 }
565
566 #[test]
567 fn describe_maintenance_windows_pagination() {
568 let svc = make_service();
569
570 for i in 0..11 {
573 let req = make_request(
574 "CreateMaintenanceWindow",
575 json!({
576 "Name": format!("test-window-{i:02}"),
577 "Schedule": "cron(0 2 ? * SUN *)",
578 "Duration": 3,
579 "Cutoff": 1,
580 "AllowUnassociatedTargets": true
581 }),
582 );
583 svc.create_maintenance_window(&req).unwrap();
584 }
585
586 let req = make_request("DescribeMaintenanceWindows", json!({ "MaxResults": 10 }));
588 let resp = svc.describe_maintenance_windows(&req).unwrap();
589 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
590 assert_eq!(body["WindowIdentities"].as_array().unwrap().len(), 10);
591 let token = body["NextToken"].as_str().unwrap();
592
593 let req = make_request(
595 "DescribeMaintenanceWindows",
596 json!({ "MaxResults": 10, "NextToken": token }),
597 );
598 let resp = svc.describe_maintenance_windows(&req).unwrap();
599 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
600 assert_eq!(body["WindowIdentities"].as_array().unwrap().len(), 1);
601 assert!(body.get("NextToken").is_none() || body["NextToken"].is_null());
602 }
603
604 #[test]
607 fn association_crud() {
608 let svc = make_service();
609
610 let req = make_request(
612 "CreateAssociation",
613 json!({
614 "Name": "AWS-RunShellScript",
615 "Targets": [{"Key": "InstanceIds", "Values": ["i-1234567890abcdef0"]}],
616 "ScheduleExpression": "rate(1 hour)",
617 "AssociationName": "my-assoc",
618 }),
619 );
620 let resp = svc.create_association(&req).unwrap();
621 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
622 let assoc_id = body["AssociationDescription"]["AssociationId"]
623 .as_str()
624 .unwrap()
625 .to_string();
626 assert_eq!(
627 body["AssociationDescription"]["Name"].as_str().unwrap(),
628 "AWS-RunShellScript"
629 );
630
631 let req = make_request("DescribeAssociation", json!({ "AssociationId": assoc_id }));
633 let resp = svc.describe_association(&req).unwrap();
634 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
635 assert_eq!(
636 body["AssociationDescription"]["AssociationName"]
637 .as_str()
638 .unwrap(),
639 "my-assoc"
640 );
641
642 let req = make_request("ListAssociations", json!({}));
644 let resp = svc.list_associations(&req).unwrap();
645 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
646 assert_eq!(body["Associations"].as_array().unwrap().len(), 1);
647
648 let req = make_request(
650 "UpdateAssociation",
651 json!({
652 "AssociationId": assoc_id,
653 "AssociationName": "updated-assoc",
654 }),
655 );
656 let resp = svc.update_association(&req).unwrap();
657 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
658 assert_eq!(
659 body["AssociationDescription"]["AssociationName"]
660 .as_str()
661 .unwrap(),
662 "updated-assoc"
663 );
664
665 let req = make_request(
667 "ListAssociationVersions",
668 json!({ "AssociationId": assoc_id }),
669 );
670 let resp = svc.list_association_versions(&req).unwrap();
671 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
672 assert_eq!(body["AssociationVersions"].as_array().unwrap().len(), 2);
673
674 let req = make_request("DeleteAssociation", json!({ "AssociationId": assoc_id }));
676 svc.delete_association(&req).unwrap();
677
678 let req = make_request("DescribeAssociation", json!({ "AssociationId": assoc_id }));
680 assert!(svc.describe_association(&req).is_err());
681 }
682
683 #[test]
684 fn association_batch_create() {
685 let svc = make_service();
686 let req = make_request(
687 "CreateAssociationBatch",
688 json!({
689 "Entries": [
690 {"Name": "AWS-RunShellScript", "Targets": [{"Key": "InstanceIds", "Values": ["i-001"]}]},
691 {"Name": "AWS-RunShellScript", "Targets": [{"Key": "InstanceIds", "Values": ["i-002"]}]},
692 ]
693 }),
694 );
695 let resp = svc.create_association_batch(&req).unwrap();
696 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
697 assert_eq!(body["Successful"].as_array().unwrap().len(), 2);
698 assert!(body["Failed"].as_array().unwrap().is_empty());
699 }
700
701 #[test]
702 fn start_associations_once_noop() {
703 let svc = make_service();
704 let req = make_request(
705 "StartAssociationsOnce",
706 json!({ "AssociationIds": ["fake-id"] }),
707 );
708 svc.start_associations_once(&req).unwrap();
709 }
710
711 #[test]
714 fn ops_item_crud() {
715 let svc = make_service();
716
717 let req = make_request(
719 "CreateOpsItem",
720 json!({
721 "Title": "Test OpsItem",
722 "Source": "test",
723 "Description": "A test ops item",
724 }),
725 );
726 let resp = svc.create_ops_item(&req).unwrap();
727 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
728 let ops_item_id = body["OpsItemId"].as_str().unwrap().to_string();
729
730 let req = make_request("GetOpsItem", json!({ "OpsItemId": ops_item_id }));
732 let resp = svc.get_ops_item(&req).unwrap();
733 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
734 assert_eq!(body["OpsItem"]["Title"].as_str().unwrap(), "Test OpsItem");
735 assert_eq!(body["OpsItem"]["Status"].as_str().unwrap(), "Open");
736
737 let req = make_request(
739 "UpdateOpsItem",
740 json!({
741 "OpsItemId": ops_item_id,
742 "Title": "Updated OpsItem",
743 "Status": "Resolved",
744 }),
745 );
746 svc.update_ops_item(&req).unwrap();
747
748 let req = make_request("GetOpsItem", json!({ "OpsItemId": ops_item_id }));
750 let resp = svc.get_ops_item(&req).unwrap();
751 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
752 assert_eq!(
753 body["OpsItem"]["Title"].as_str().unwrap(),
754 "Updated OpsItem"
755 );
756 assert_eq!(body["OpsItem"]["Status"].as_str().unwrap(), "Resolved");
757
758 let req = make_request("DescribeOpsItems", json!({}));
760 let resp = svc.describe_ops_items(&req).unwrap();
761 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
762 assert_eq!(body["OpsItemSummaries"].as_array().unwrap().len(), 1);
763
764 let req = make_request("DeleteOpsItem", json!({ "OpsItemId": ops_item_id }));
766 svc.delete_ops_item(&req).unwrap();
767
768 let req = make_request("GetOpsItem", json!({ "OpsItemId": ops_item_id }));
770 assert!(svc.get_ops_item(&req).is_err());
771 }
772
773 #[test]
776 fn resource_policy_crud() {
777 let svc = make_service();
778 let resource_arn = "arn:aws:ssm:us-east-1:123456789012:parameter/test";
779
780 let req = make_request(
782 "PutResourcePolicy",
783 json!({
784 "ResourceArn": resource_arn,
785 "Policy": r#"{"Version":"2012-10-17","Statement":[]}"#,
786 }),
787 );
788 let resp = svc.put_resource_policy(&req).unwrap();
789 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
790 let policy_id = body["PolicyId"].as_str().unwrap().to_string();
791 let policy_hash = body["PolicyHash"].as_str().unwrap().to_string();
792
793 let req = make_request(
795 "GetResourcePolicies",
796 json!({ "ResourceArn": resource_arn }),
797 );
798 let resp = svc.get_resource_policies(&req).unwrap();
799 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
800 assert_eq!(body["Policies"].as_array().unwrap().len(), 1);
801
802 let req = make_request(
804 "DeleteResourcePolicy",
805 json!({
806 "ResourceArn": resource_arn,
807 "PolicyId": policy_id,
808 "PolicyHash": policy_hash,
809 }),
810 );
811 svc.delete_resource_policy(&req).unwrap();
812
813 let req = make_request(
815 "GetResourcePolicies",
816 json!({ "ResourceArn": resource_arn }),
817 );
818 let resp = svc.get_resource_policies(&req).unwrap();
819 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
820 assert!(body["Policies"].as_array().unwrap().is_empty());
821 }
822
823 #[test]
826 fn get_connection_status_returns_connected() {
827 let svc = make_service();
828 let req = make_request(
829 "GetConnectionStatus",
830 json!({ "Target": "i-1234567890abcdef0" }),
831 );
832 let resp = svc.get_connection_status(&req).unwrap();
833 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
834 assert_eq!(body["Status"].as_str().unwrap(), "connected");
835 }
836
837 #[test]
838 fn get_calendar_state_returns_open() {
839 let svc = make_service();
840 let req = make_request(
841 "GetCalendarState",
842 json!({ "CalendarNames": ["arn:aws:ssm:us-east-1:123456789012:document/cal"] }),
843 );
844 let resp = svc.get_calendar_state(&req).unwrap();
845 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
846 assert_eq!(body["State"].as_str().unwrap(), "OPEN");
847 }
848
849 #[test]
850 fn service_setting_crud() {
851 let svc = make_service();
852
853 let req = make_request(
855 "GetServiceSetting",
856 json!({ "SettingId": "/ssm/parameter-store/high-throughput-enabled" }),
857 );
858 let resp = svc.get_service_setting(&req).unwrap();
859 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
860 assert_eq!(
861 body["ServiceSetting"]["Status"].as_str().unwrap(),
862 "Default"
863 );
864
865 let req = make_request(
867 "UpdateServiceSetting",
868 json!({
869 "SettingId": "/ssm/parameter-store/high-throughput-enabled",
870 "SettingValue": "true",
871 }),
872 );
873 svc.update_service_setting(&req).unwrap();
874
875 let req = make_request(
877 "GetServiceSetting",
878 json!({ "SettingId": "/ssm/parameter-store/high-throughput-enabled" }),
879 );
880 let resp = svc.get_service_setting(&req).unwrap();
881 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
882 assert_eq!(
883 body["ServiceSetting"]["Status"].as_str().unwrap(),
884 "Customized"
885 );
886 assert_eq!(
887 body["ServiceSetting"]["SettingValue"].as_str().unwrap(),
888 "true"
889 );
890
891 let req = make_request(
893 "ResetServiceSetting",
894 json!({ "SettingId": "/ssm/parameter-store/high-throughput-enabled" }),
895 );
896 svc.reset_service_setting(&req).unwrap();
897
898 let req = make_request(
900 "GetServiceSetting",
901 json!({ "SettingId": "/ssm/parameter-store/high-throughput-enabled" }),
902 );
903 let resp = svc.get_service_setting(&req).unwrap();
904 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
905 assert_eq!(
906 body["ServiceSetting"]["Status"].as_str().unwrap(),
907 "Default"
908 );
909 }
910
911 #[test]
912 fn list_document_versions_works() {
913 let svc = make_service();
914
915 let req = make_request(
917 "CreateDocument",
918 json!({
919 "Name": "TestDocVer",
920 "Content": r#"{"schemaVersion":"2.2","mainSteps":[]}"#,
921 "DocumentType": "Command",
922 }),
923 );
924 svc.create_document(&req).unwrap();
925
926 let req = make_request("ListDocumentVersions", json!({ "Name": "TestDocVer" }));
928 let resp = svc.list_document_versions(&req).unwrap();
929 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
930 assert!(!body["DocumentVersions"].as_array().unwrap().is_empty());
931 }
932
933 #[test]
934 fn describe_patch_group_state_returns_zeros() {
935 let svc = make_service();
936 let req = make_request(
937 "DescribePatchGroupState",
938 json!({ "PatchGroup": "test-group" }),
939 );
940 let resp = svc.describe_patch_group_state(&req).unwrap();
941 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
942 assert_eq!(body["Instances"].as_i64().unwrap(), 0);
943 }
944
945 #[test]
946 fn get_default_patch_baseline_works() {
947 let svc = make_service();
948 let req = make_request(
949 "GetDefaultPatchBaseline",
950 json!({ "OperatingSystem": "WINDOWS" }),
951 );
952 let resp = svc.get_default_patch_baseline(&req).unwrap();
953 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
954 assert!(body["BaselineId"].is_string());
955 }
956
957 #[test]
958 fn describe_available_patches_returns_empty() {
959 let svc = make_service();
960 let req = make_request("DescribeAvailablePatches", json!({}));
961 let resp = svc.describe_available_patches(&req).unwrap();
962 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
963 assert!(body["Patches"].as_array().unwrap().is_empty());
964 }
965
966 #[test]
967 fn describe_patch_properties_returns_empty() {
968 let svc = make_service();
969 let req = make_request(
970 "DescribePatchProperties",
971 json!({ "OperatingSystem": "WINDOWS", "Property": "PRODUCT" }),
972 );
973 let resp = svc.describe_patch_properties(&req).unwrap();
974 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
975 assert!(body["Properties"].as_array().unwrap().is_empty());
976 }
977
978 #[test]
981 fn inventory_lifecycle() {
982 let svc = make_service();
983
984 let req = make_request(
986 "PutInventory",
987 json!({
988 "InstanceId": "i-1234567890abcdef0",
989 "Items": [{
990 "TypeName": "AWS:Application",
991 "SchemaVersion": "1.1",
992 "CaptureTime": "2024-01-01T00:00:00Z",
993 "Content": [
994 {"Name": "TestApp", "Version": "1.0"},
995 {"Name": "AnotherApp", "Version": "2.0"},
996 ]
997 }]
998 }),
999 );
1000 svc.put_inventory(&req).unwrap();
1001
1002 let req = make_request("GetInventory", json!({}));
1004 let resp = svc.get_inventory(&req).unwrap();
1005 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1006 assert_eq!(body["Entities"].as_array().unwrap().len(), 1);
1007 assert_eq!(
1008 body["Entities"][0]["Id"].as_str().unwrap(),
1009 "i-1234567890abcdef0"
1010 );
1011
1012 let req = make_request(
1014 "ListInventoryEntries",
1015 json!({
1016 "InstanceId": "i-1234567890abcdef0",
1017 "TypeName": "AWS:Application",
1018 }),
1019 );
1020 let resp = svc.list_inventory_entries(&req).unwrap();
1021 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1022 assert_eq!(body["Entries"].as_array().unwrap().len(), 2);
1023 assert_eq!(body["TypeName"].as_str().unwrap(), "AWS:Application");
1024
1025 let req = make_request("GetInventorySchema", json!({}));
1027 let resp = svc.get_inventory_schema(&req).unwrap();
1028 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1029 assert!(!body["Schemas"].as_array().unwrap().is_empty());
1030
1031 let req = make_request("DeleteInventory", json!({ "TypeName": "AWS:Application" }));
1033 let resp = svc.delete_inventory(&req).unwrap();
1034 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1035 assert!(body["DeletionId"].is_string());
1036
1037 let req = make_request("DescribeInventoryDeletions", json!({}));
1039 let resp = svc.describe_inventory_deletions(&req).unwrap();
1040 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1041 assert_eq!(body["InventoryDeletions"].as_array().unwrap().len(), 1);
1042
1043 let req = make_request(
1045 "ListInventoryEntries",
1046 json!({
1047 "InstanceId": "i-1234567890abcdef0",
1048 "TypeName": "AWS:Application",
1049 }),
1050 );
1051 let resp = svc.list_inventory_entries(&req).unwrap();
1052 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1053 assert!(body["Entries"].as_array().unwrap().is_empty());
1054 }
1055
1056 #[test]
1059 fn compliance_lifecycle() {
1060 let svc = make_service();
1061
1062 let req = make_request(
1064 "PutComplianceItems",
1065 json!({
1066 "ResourceId": "i-1234567890abcdef0",
1067 "ResourceType": "ManagedInstance",
1068 "ComplianceType": "Custom:PatchTest",
1069 "ExecutionSummary": {
1070 "ExecutionTime": "2024-01-01T00:00:00Z",
1071 },
1072 "Items": [
1073 {
1074 "Id": "patch-1",
1075 "Title": "Security patch 1",
1076 "Severity": "CRITICAL",
1077 "Status": "COMPLIANT",
1078 },
1079 {
1080 "Id": "patch-2",
1081 "Title": "Security patch 2",
1082 "Severity": "HIGH",
1083 "Status": "NON_COMPLIANT",
1084 },
1085 ],
1086 }),
1087 );
1088 svc.put_compliance_items(&req).unwrap();
1089
1090 let req = make_request("ListComplianceItems", json!({}));
1092 let resp = svc.list_compliance_items(&req).unwrap();
1093 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1094 assert_eq!(body["ComplianceItems"].as_array().unwrap().len(), 2);
1095
1096 let req = make_request("ListComplianceSummaries", json!({}));
1098 let resp = svc.list_compliance_summaries(&req).unwrap();
1099 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1100 assert_eq!(body["ComplianceSummaryItems"].as_array().unwrap().len(), 1);
1101
1102 let req = make_request("ListResourceComplianceSummaries", json!({}));
1104 let resp = svc.list_resource_compliance_summaries(&req).unwrap();
1105 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1106 assert_eq!(
1107 body["ResourceComplianceSummaryItems"]
1108 .as_array()
1109 .unwrap()
1110 .len(),
1111 1
1112 );
1113 }
1114
1115 fn create_mw_with_target_and_task(svc: &SsmService) -> (String, String, String) {
1118 let req = make_request(
1120 "CreateMaintenanceWindow",
1121 json!({
1122 "Name": "test-mw",
1123 "Schedule": "cron(0 2 ? * SUN *)",
1124 "Duration": 3,
1125 "Cutoff": 1,
1126 "AllowUnassociatedTargets": true,
1127 }),
1128 );
1129 let resp = svc.create_maintenance_window(&req).unwrap();
1130 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1131 let window_id = body["WindowId"].as_str().unwrap().to_string();
1132
1133 let req = make_request(
1135 "RegisterTargetWithMaintenanceWindow",
1136 json!({
1137 "WindowId": window_id,
1138 "ResourceType": "INSTANCE",
1139 "Targets": [{"Key": "InstanceIds", "Values": ["i-001"]}],
1140 "Name": "test-target",
1141 }),
1142 );
1143 let resp = svc.register_target_with_maintenance_window(&req).unwrap();
1144 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1145 let target_id = body["WindowTargetId"].as_str().unwrap().to_string();
1146
1147 let req = make_request(
1149 "RegisterTaskWithMaintenanceWindow",
1150 json!({
1151 "WindowId": window_id,
1152 "TaskArn": "AWS-RunShellScript",
1153 "TaskType": "RUN_COMMAND",
1154 "Targets": [{"Key": "WindowTargetIds", "Values": [target_id]}],
1155 "Name": "test-task",
1156 }),
1157 );
1158 let resp = svc.register_task_with_maintenance_window(&req).unwrap();
1159 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1160 let task_id = body["WindowTaskId"].as_str().unwrap().to_string();
1161
1162 (window_id, target_id, task_id)
1163 }
1164
1165 #[test]
1166 fn maintenance_window_update_target_and_task() {
1167 let svc = make_service();
1168 let (window_id, target_id, task_id) = create_mw_with_target_and_task(&svc);
1169
1170 let req = make_request(
1172 "UpdateMaintenanceWindowTarget",
1173 json!({
1174 "WindowId": window_id,
1175 "WindowTargetId": target_id,
1176 "Name": "updated-target",
1177 }),
1178 );
1179 let resp = svc.update_maintenance_window_target(&req).unwrap();
1180 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1181 assert_eq!(body["Name"].as_str().unwrap(), "updated-target");
1182
1183 let req = make_request(
1185 "GetMaintenanceWindowTask",
1186 json!({
1187 "WindowId": window_id,
1188 "WindowTaskId": task_id,
1189 }),
1190 );
1191 let resp = svc.get_maintenance_window_task(&req).unwrap();
1192 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1193 assert_eq!(body["TaskArn"].as_str().unwrap(), "AWS-RunShellScript");
1194 assert_eq!(body["Name"].as_str().unwrap(), "test-task");
1195
1196 let req = make_request(
1198 "UpdateMaintenanceWindowTask",
1199 json!({
1200 "WindowId": window_id,
1201 "WindowTaskId": task_id,
1202 "Name": "updated-task",
1203 "MaxConcurrency": "10",
1204 }),
1205 );
1206 let resp = svc.update_maintenance_window_task(&req).unwrap();
1207 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1208 assert_eq!(body["Name"].as_str().unwrap(), "updated-task");
1209 assert_eq!(body["MaxConcurrency"].as_str().unwrap(), "10");
1210 }
1211
1212 #[test]
1213 fn maintenance_window_execution_lifecycle() {
1214 let svc = make_service();
1215 let (window_id, _, _) = create_mw_with_target_and_task(&svc);
1216
1217 let exec_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
1218 let task_exec_id = "11111111-2222-3333-4444-555555555555";
1219
1220 {
1222 let now = chrono::Utc::now();
1223 let mut state = svc.state.write();
1224 let exec = crate::state::MaintenanceWindowExecution {
1225 window_execution_id: exec_id.to_string(),
1226 window_id: window_id.clone(),
1227 status: "IN_PROGRESS".to_string(),
1228 start_time: now,
1229 end_time: None,
1230 tasks: vec![crate::state::MaintenanceWindowExecutionTask {
1231 task_execution_id: task_exec_id.to_string(),
1232 window_execution_id: exec_id.to_string(),
1233 task_arn: "AWS-RunShellScript".to_string(),
1234 task_type: "RUN_COMMAND".to_string(),
1235 status: "IN_PROGRESS".to_string(),
1236 start_time: now,
1237 end_time: None,
1238 invocations: vec![crate::state::MaintenanceWindowExecutionTaskInvocation {
1239 invocation_id: "inv-001".to_string(),
1240 task_execution_id: task_exec_id.to_string(),
1241 window_execution_id: exec_id.to_string(),
1242 execution_id: Some("cmd-001".to_string()),
1243 status: "IN_PROGRESS".to_string(),
1244 start_time: now,
1245 end_time: None,
1246 parameters: None,
1247 owner_information: None,
1248 window_target_id: None,
1249 status_details: None,
1250 }],
1251 }],
1252 };
1253 state.maintenance_window_executions.push(exec);
1254 }
1255
1256 let req = make_request(
1258 "DescribeMaintenanceWindowExecutions",
1259 json!({ "WindowId": window_id }),
1260 );
1261 let resp = svc.describe_maintenance_window_executions(&req).unwrap();
1262 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1263 assert_eq!(body["WindowExecutions"].as_array().unwrap().len(), 1);
1264
1265 let req = make_request(
1267 "GetMaintenanceWindowExecution",
1268 json!({ "WindowExecutionId": exec_id }),
1269 );
1270 let resp = svc.get_maintenance_window_execution(&req).unwrap();
1271 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1272 assert_eq!(body["Status"].as_str().unwrap(), "IN_PROGRESS");
1273
1274 let req = make_request(
1276 "DescribeMaintenanceWindowExecutionTasks",
1277 json!({ "WindowExecutionId": exec_id }),
1278 );
1279 let resp = svc
1280 .describe_maintenance_window_execution_tasks(&req)
1281 .unwrap();
1282 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1283 assert_eq!(
1284 body["WindowExecutionTaskIdentities"]
1285 .as_array()
1286 .unwrap()
1287 .len(),
1288 1
1289 );
1290
1291 let req = make_request(
1293 "GetMaintenanceWindowExecutionTask",
1294 json!({
1295 "WindowExecutionId": exec_id,
1296 "TaskId": task_exec_id,
1297 }),
1298 );
1299 let resp = svc.get_maintenance_window_execution_task(&req).unwrap();
1300 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1301 assert_eq!(body["TaskArn"].as_str().unwrap(), "AWS-RunShellScript");
1302
1303 let req = make_request(
1305 "DescribeMaintenanceWindowExecutionTaskInvocations",
1306 json!({
1307 "WindowExecutionId": exec_id,
1308 "TaskId": task_exec_id,
1309 }),
1310 );
1311 let resp = svc
1312 .describe_maintenance_window_execution_task_invocations(&req)
1313 .unwrap();
1314 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1315 assert_eq!(
1316 body["WindowExecutionTaskInvocationIdentities"]
1317 .as_array()
1318 .unwrap()
1319 .len(),
1320 1
1321 );
1322
1323 let req = make_request(
1325 "GetMaintenanceWindowExecutionTaskInvocation",
1326 json!({
1327 "WindowExecutionId": exec_id,
1328 "TaskId": task_exec_id,
1329 "InvocationId": "inv-001",
1330 }),
1331 );
1332 let resp = svc
1333 .get_maintenance_window_execution_task_invocation(&req)
1334 .unwrap();
1335 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1336 assert_eq!(body["ExecutionId"].as_str().unwrap(), "cmd-001");
1337
1338 let req = make_request(
1340 "CancelMaintenanceWindowExecution",
1341 json!({ "WindowExecutionId": exec_id }),
1342 );
1343 let resp = svc.cancel_maintenance_window_execution(&req).unwrap();
1344 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1345 assert_eq!(body["WindowExecutionId"].as_str().unwrap(), exec_id);
1346
1347 let req = make_request("DescribeMaintenanceWindowSchedule", json!({}));
1349 let resp = svc.describe_maintenance_window_schedule(&req).unwrap();
1350 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1351 assert!(body["ScheduledWindowExecutions"]
1352 .as_array()
1353 .unwrap()
1354 .is_empty());
1355
1356 let req = make_request(
1358 "DescribeMaintenanceWindowsForTarget",
1359 json!({
1360 "ResourceType": "INSTANCE",
1361 "Targets": [{"Key": "InstanceIds", "Values": ["i-001"]}],
1362 }),
1363 );
1364 let resp = svc.describe_maintenance_windows_for_target(&req).unwrap();
1365 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1366 assert_eq!(body["WindowIdentities"].as_array().unwrap().len(), 1);
1367 }
1368
1369 #[test]
1372 fn update_patch_baseline_works() {
1373 let svc = make_service();
1374
1375 let req = make_request(
1377 "CreatePatchBaseline",
1378 json!({
1379 "Name": "test-baseline",
1380 "OperatingSystem": "AMAZON_LINUX_2",
1381 "Description": "original description",
1382 }),
1383 );
1384 let resp = svc.create_patch_baseline(&req).unwrap();
1385 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1386 let baseline_id = body["BaselineId"].as_str().unwrap().to_string();
1387
1388 let req = make_request(
1390 "UpdatePatchBaseline",
1391 json!({
1392 "BaselineId": baseline_id,
1393 "Name": "updated-baseline",
1394 "Description": "updated description",
1395 "ApprovedPatches": ["KB001", "KB002"],
1396 }),
1397 );
1398 let resp = svc.update_patch_baseline(&req).unwrap();
1399 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1400 assert_eq!(body["Name"].as_str().unwrap(), "updated-baseline");
1401 assert_eq!(body["Description"].as_str().unwrap(), "updated description");
1402 assert_eq!(body["ApprovedPatches"].as_array().unwrap().len(), 2);
1403 }
1404
1405 #[test]
1408 fn resource_data_sync_lifecycle() {
1409 let svc = make_service();
1410
1411 let req = make_request(
1413 "CreateResourceDataSync",
1414 json!({
1415 "SyncName": "test-sync",
1416 "SyncType": "SyncFromSource",
1417 "SyncSource": {
1418 "SourceType": "AWS",
1419 "SourceRegions": ["us-east-1"],
1420 },
1421 }),
1422 );
1423 svc.create_resource_data_sync(&req).unwrap();
1424
1425 let req = make_request("ListResourceDataSync", json!({}));
1427 let resp = svc.list_resource_data_sync(&req).unwrap();
1428 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1429 assert_eq!(body["ResourceDataSyncItems"].as_array().unwrap().len(), 1);
1430 assert_eq!(
1431 body["ResourceDataSyncItems"][0]["SyncName"]
1432 .as_str()
1433 .unwrap(),
1434 "test-sync"
1435 );
1436
1437 let req = make_request(
1439 "UpdateResourceDataSync",
1440 json!({
1441 "SyncName": "test-sync",
1442 "SyncType": "SyncFromSource",
1443 "SyncSource": {
1444 "SourceType": "AWS",
1445 "SourceRegions": ["us-east-1", "us-west-2"],
1446 },
1447 }),
1448 );
1449 svc.update_resource_data_sync(&req).unwrap();
1450
1451 let req = make_request("DeleteResourceDataSync", json!({ "SyncName": "test-sync" }));
1453 svc.delete_resource_data_sync(&req).unwrap();
1454
1455 let req = make_request("ListResourceDataSync", json!({}));
1457 let resp = svc.list_resource_data_sync(&req).unwrap();
1458 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1459 assert!(body["ResourceDataSyncItems"].as_array().unwrap().is_empty());
1460 }
1461
1462 #[test]
1465 fn describe_instance_patch_states_returns_empty() {
1466 let svc = make_service();
1467 let req = make_request(
1468 "DescribeInstancePatchStates",
1469 json!({ "InstanceIds": ["i-001"] }),
1470 );
1471 let resp = svc.describe_instance_patch_states(&req).unwrap();
1472 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1473 assert!(body["InstancePatchStates"].as_array().unwrap().is_empty());
1474 }
1475
1476 #[test]
1477 fn describe_instance_patches_returns_empty() {
1478 let svc = make_service();
1479 let req = make_request("DescribeInstancePatches", json!({ "InstanceId": "i-001" }));
1480 let resp = svc.describe_instance_patches(&req).unwrap();
1481 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1482 assert!(body["Patches"].as_array().unwrap().is_empty());
1483 }
1484
1485 #[test]
1486 fn describe_effective_patches_returns_empty() {
1487 let svc = make_service();
1488 let req = make_request(
1489 "DescribeEffectivePatchesForPatchBaseline",
1490 json!({ "BaselineId": "pb-12345678901234567" }),
1491 );
1492 let resp = svc
1493 .describe_effective_patches_for_patch_baseline(&req)
1494 .unwrap();
1495 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1496 assert!(body["EffectivePatches"].as_array().unwrap().is_empty());
1497 }
1498
1499 #[test]
1500 fn get_ops_summary_returns_empty() {
1501 let svc = make_service();
1502 let req = make_request("GetOpsSummary", json!({}));
1503 let resp = svc.get_ops_summary(&req).unwrap();
1504 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1505 assert!(body["Entities"].as_array().unwrap().is_empty());
1506 }
1507
1508 #[test]
1511 fn associate_and_disassociate_ops_item_related_item() {
1512 let svc = make_service();
1513 let req = make_request(
1515 "CreateOpsItem",
1516 json!({ "Title": "Test", "Source": "test", "Description": "test desc" }),
1517 );
1518 let resp = svc.create_ops_item(&req).unwrap();
1519 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1520 let ops_item_id = body["OpsItemId"].as_str().unwrap().to_string();
1521
1522 let req = make_request(
1524 "AssociateOpsItemRelatedItem",
1525 json!({
1526 "OpsItemId": ops_item_id,
1527 "AssociationType": "IsParentOf",
1528 "ResourceType": "AWS::SSMIncidents::IncidentRecord",
1529 "ResourceUri": "arn:aws:ssm-incidents::123456789012:incident-record/test"
1530 }),
1531 );
1532 let resp = svc.associate_ops_item_related_item(&req).unwrap();
1533 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1534 let assoc_id = body["AssociationId"].as_str().unwrap().to_string();
1535
1536 let req = make_request(
1538 "ListOpsItemRelatedItems",
1539 json!({ "OpsItemId": ops_item_id }),
1540 );
1541 let resp = svc.list_ops_item_related_items(&req).unwrap();
1542 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1543 assert_eq!(body["Summaries"].as_array().unwrap().len(), 1);
1544
1545 let req = make_request(
1547 "DisassociateOpsItemRelatedItem",
1548 json!({ "OpsItemId": ops_item_id, "AssociationId": assoc_id }),
1549 );
1550 svc.disassociate_ops_item_related_item(&req).unwrap();
1551 }
1552
1553 #[test]
1554 fn list_ops_item_events_returns_empty() {
1555 let svc = make_service();
1556 let req = make_request("ListOpsItemEvents", json!({}));
1557 let resp = svc.list_ops_item_events(&req).unwrap();
1558 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1559 assert!(body["Summaries"].as_array().unwrap().is_empty());
1560 }
1561
1562 #[test]
1565 fn ops_metadata_lifecycle() {
1566 let svc = make_service();
1567
1568 let req = make_request(
1570 "CreateOpsMetadata",
1571 json!({
1572 "ResourceId": "test-resource",
1573 "Metadata": { "key1": { "Value": "val1" } }
1574 }),
1575 );
1576 let resp = svc.create_ops_metadata(&req).unwrap();
1577 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1578 let arn = body["OpsMetadataArn"].as_str().unwrap().to_string();
1579
1580 let req = make_request("GetOpsMetadata", json!({ "OpsMetadataArn": arn }));
1582 let resp = svc.get_ops_metadata(&req).unwrap();
1583 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1584 assert_eq!(body["ResourceId"].as_str().unwrap(), "test-resource");
1585
1586 let req = make_request(
1588 "UpdateOpsMetadata",
1589 json!({
1590 "OpsMetadataArn": arn,
1591 "MetadataToUpdate": { "key2": { "Value": "val2" } }
1592 }),
1593 );
1594 svc.update_ops_metadata(&req).unwrap();
1595
1596 let req = make_request("ListOpsMetadata", json!({}));
1598 let resp = svc.list_ops_metadata(&req).unwrap();
1599 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1600 assert_eq!(body["OpsMetadataList"].as_array().unwrap().len(), 1);
1601
1602 let req = make_request("DeleteOpsMetadata", json!({ "OpsMetadataArn": arn }));
1604 svc.delete_ops_metadata(&req).unwrap();
1605 }
1606
1607 #[test]
1610 fn automation_execution_lifecycle() {
1611 let svc = make_service();
1612
1613 let req = make_request(
1615 "StartAutomationExecution",
1616 json!({ "DocumentName": "AWS-RunShellScript" }),
1617 );
1618 let resp = svc.start_automation_execution(&req).unwrap();
1619 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1620 let exec_id = body["AutomationExecutionId"].as_str().unwrap().to_string();
1621
1622 let req = make_request(
1624 "GetAutomationExecution",
1625 json!({ "AutomationExecutionId": exec_id }),
1626 );
1627 let resp = svc.get_automation_execution(&req).unwrap();
1628 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1629 assert_eq!(
1630 body["AutomationExecution"]["AutomationExecutionStatus"]
1631 .as_str()
1632 .unwrap(),
1633 "InProgress"
1634 );
1635
1636 let req = make_request("DescribeAutomationExecutions", json!({}));
1638 let resp = svc.describe_automation_executions(&req).unwrap();
1639 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1640 assert_eq!(
1641 body["AutomationExecutionMetadataList"]
1642 .as_array()
1643 .unwrap()
1644 .len(),
1645 1
1646 );
1647
1648 let req = make_request(
1650 "DescribeAutomationStepExecutions",
1651 json!({ "AutomationExecutionId": exec_id }),
1652 );
1653 let resp = svc.describe_automation_step_executions(&req).unwrap();
1654 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1655 assert!(body["StepExecutions"].as_array().unwrap().is_empty());
1656
1657 let req = make_request(
1659 "SendAutomationSignal",
1660 json!({ "AutomationExecutionId": exec_id, "SignalType": "Approve" }),
1661 );
1662 svc.send_automation_signal(&req).unwrap();
1663
1664 let req = make_request(
1666 "StopAutomationExecution",
1667 json!({ "AutomationExecutionId": exec_id }),
1668 );
1669 svc.stop_automation_execution(&req).unwrap();
1670 }
1671
1672 #[test]
1673 fn start_change_request_execution_works() {
1674 let svc = make_service();
1675 let req = make_request(
1676 "StartChangeRequestExecution",
1677 json!({
1678 "DocumentName": "AWS-ChangeManager",
1679 "Runbooks": [{ "DocumentName": "AWS-RunShellScript" }]
1680 }),
1681 );
1682 let resp = svc.start_change_request_execution(&req).unwrap();
1683 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1684 assert!(body["AutomationExecutionId"].as_str().is_some());
1685 }
1686
1687 #[test]
1688 fn execution_preview_lifecycle() {
1689 let svc = make_service();
1690
1691 let req = make_request(
1692 "StartExecutionPreview",
1693 json!({ "DocumentName": "AWS-RunShellScript" }),
1694 );
1695 let resp = svc.start_execution_preview(&req).unwrap();
1696 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1697 let preview_id = body["ExecutionPreviewId"].as_str().unwrap().to_string();
1698
1699 let req = make_request(
1700 "GetExecutionPreview",
1701 json!({ "ExecutionPreviewId": preview_id }),
1702 );
1703 let resp = svc.get_execution_preview(&req).unwrap();
1704 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1705 assert_eq!(body["Status"].as_str().unwrap(), "Success");
1706 }
1707
1708 #[test]
1711 fn session_lifecycle() {
1712 let svc = make_service();
1713
1714 let req = make_request("StartSession", json!({ "Target": "i-001" }));
1716 let resp = svc.start_session(&req).unwrap();
1717 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1718 let session_id = body["SessionId"].as_str().unwrap().to_string();
1719 assert!(body["TokenValue"].as_str().is_some());
1720
1721 let req = make_request("ResumeSession", json!({ "SessionId": session_id }));
1723 let resp = svc.resume_session(&req).unwrap();
1724 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1725 assert_eq!(body["SessionId"].as_str().unwrap(), session_id);
1726
1727 let req = make_request("DescribeSessions", json!({ "State": "Active" }));
1729 let resp = svc.describe_sessions(&req).unwrap();
1730 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1731 assert_eq!(body["Sessions"].as_array().unwrap().len(), 1);
1732
1733 let req = make_request("TerminateSession", json!({ "SessionId": session_id }));
1735 svc.terminate_session(&req).unwrap();
1736
1737 let req = make_request("DescribeSessions", json!({ "State": "History" }));
1739 let resp = svc.describe_sessions(&req).unwrap();
1740 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1741 assert_eq!(body["Sessions"].as_array().unwrap().len(), 1);
1742 }
1743
1744 #[test]
1745 fn start_access_request_and_get_token() {
1746 let svc = make_service();
1747
1748 let req = make_request(
1749 "StartAccessRequest",
1750 json!({ "Reason": "test", "Targets": [{"Key": "InstanceIds", "Values": ["i-001"]}] }),
1751 );
1752 let resp = svc.start_access_request(&req).unwrap();
1753 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1754 let ar_id = body["AccessRequestId"].as_str().unwrap().to_string();
1755
1756 let req = make_request("GetAccessToken", json!({ "AccessRequestId": ar_id }));
1757 let resp = svc.get_access_token(&req).unwrap();
1758 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1759 assert!(body["Credentials"]["AccessKeyId"].as_str().is_some());
1760 assert_eq!(body["AccessRequestStatus"].as_str(), Some("Approved"));
1761 }
1762
1763 #[test]
1766 fn activation_lifecycle() {
1767 let svc = make_service();
1768
1769 let req = make_request(
1771 "CreateActivation",
1772 json!({ "IamRole": "SSMServiceRole", "Description": "test" }),
1773 );
1774 let resp = svc.create_activation(&req).unwrap();
1775 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1776 let activation_id = body["ActivationId"].as_str().unwrap().to_string();
1777 assert!(body["ActivationCode"].as_str().is_some());
1778
1779 let req = make_request("DescribeActivations", json!({}));
1781 let resp = svc.describe_activations(&req).unwrap();
1782 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1783 assert_eq!(body["ActivationList"].as_array().unwrap().len(), 1);
1784
1785 let req = make_request("DeleteActivation", json!({ "ActivationId": activation_id }));
1787 svc.delete_activation(&req).unwrap();
1788 }
1789
1790 #[test]
1791 fn deregister_managed_instance_no_error() {
1792 let svc = make_service();
1793 let req = make_request(
1794 "DeregisterManagedInstance",
1795 json!({ "InstanceId": "mi-01234567890123456" }),
1796 );
1797 svc.deregister_managed_instance(&req).unwrap();
1798 }
1799
1800 #[test]
1801 fn describe_instance_information_empty() {
1802 let svc = make_service();
1803 let req = make_request("DescribeInstanceInformation", json!({}));
1804 let resp = svc.describe_instance_information(&req).unwrap();
1805 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1806 assert!(body["InstanceInformationList"]
1807 .as_array()
1808 .unwrap()
1809 .is_empty());
1810 }
1811
1812 #[test]
1813 fn describe_instance_properties_empty() {
1814 let svc = make_service();
1815 let req = make_request("DescribeInstanceProperties", json!({}));
1816 let resp = svc.describe_instance_properties(&req).unwrap();
1817 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1818 assert!(body["InstanceProperties"].as_array().unwrap().is_empty());
1819 }
1820
1821 #[test]
1824 fn list_nodes_returns_empty() {
1825 let svc = make_service();
1826 let req = make_request("ListNodes", json!({}));
1827 let resp = svc.list_nodes(&req).unwrap();
1828 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1829 assert!(body["Nodes"].as_array().unwrap().is_empty());
1830 }
1831
1832 #[test]
1833 fn list_nodes_summary_returns_empty() {
1834 let svc = make_service();
1835 let req = make_request(
1836 "ListNodesSummary",
1837 json!({ "Aggregators": [{"AggregatorType": "Count"}] }),
1838 );
1839 let resp = svc.list_nodes_summary(&req).unwrap();
1840 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1841 assert!(body["Summary"].as_array().unwrap().is_empty());
1842 }
1843
1844 #[test]
1845 fn describe_effective_instance_associations_empty() {
1846 let svc = make_service();
1847 let req = make_request(
1848 "DescribeEffectiveInstanceAssociations",
1849 json!({ "InstanceId": "i-001" }),
1850 );
1851 let resp = svc.describe_effective_instance_associations(&req).unwrap();
1852 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1853 assert!(body["Associations"].as_array().unwrap().is_empty());
1854 }
1855
1856 #[test]
1857 fn describe_instance_associations_status_empty() {
1858 let svc = make_service();
1859 let req = make_request(
1860 "DescribeInstanceAssociationsStatus",
1861 json!({ "InstanceId": "i-001" }),
1862 );
1863 let resp = svc.describe_instance_associations_status(&req).unwrap();
1864 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1865 assert!(body["InstanceAssociationStatusInfos"]
1866 .as_array()
1867 .unwrap()
1868 .is_empty());
1869 }
1870
1871 fn put_param(svc: &SsmService, name: &str, value: &str) -> i64 {
1874 let req = make_request(
1875 "PutParameter",
1876 json!({
1877 "Name": name,
1878 "Value": value,
1879 "Type": "String",
1880 "Overwrite": true,
1881 }),
1882 );
1883 let resp = svc.put_parameter(&req).unwrap();
1884 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1885 body["Version"].as_i64().unwrap()
1886 }
1887
1888 #[test]
1889 fn label_and_unlabel_parameter_version() {
1890 let svc = make_service();
1891
1892 put_param(&svc, "/label/test", "v1");
1894 put_param(&svc, "/label/test", "v2");
1895
1896 let req = make_request(
1898 "LabelParameterVersion",
1899 json!({
1900 "Name": "/label/test",
1901 "ParameterVersion": 1,
1902 "Labels": ["prod", "stable"],
1903 }),
1904 );
1905 let resp = svc.label_parameter_version(&req).unwrap();
1906 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1907 assert!(body["InvalidLabels"].as_array().unwrap().is_empty());
1908 assert_eq!(body["ParameterVersion"].as_i64().unwrap(), 1);
1909
1910 let req = make_request("GetParameterHistory", json!({ "Name": "/label/test" }));
1912 let resp = svc.get_parameter_history(&req).unwrap();
1913 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1914 let params = body["Parameters"].as_array().unwrap();
1915 let v1 = params
1916 .iter()
1917 .find(|p| p["Version"].as_i64() == Some(1))
1918 .unwrap();
1919 let labels = v1["Labels"].as_array().unwrap();
1920 assert!(labels.iter().any(|l| l.as_str() == Some("prod")));
1921 assert!(labels.iter().any(|l| l.as_str() == Some("stable")));
1922
1923 let req = make_request(
1925 "UnlabelParameterVersion",
1926 json!({
1927 "Name": "/label/test",
1928 "ParameterVersion": 1,
1929 "Labels": ["prod"],
1930 }),
1931 );
1932 let resp = svc.unlabel_parameter_version(&req).unwrap();
1933 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1934 assert!(body["InvalidLabels"].as_array().unwrap().is_empty());
1935 let removed = body["RemovedLabels"].as_array().unwrap();
1936 assert_eq!(removed.len(), 1);
1937 assert_eq!(removed[0].as_str().unwrap(), "prod");
1938 }
1939
1940 #[test]
1941 fn label_parameter_version_defaults_to_latest() {
1942 let svc = make_service();
1943 put_param(&svc, "/label/default", "v1");
1944 put_param(&svc, "/label/default", "v2");
1945
1946 let req = make_request(
1948 "LabelParameterVersion",
1949 json!({
1950 "Name": "/label/default",
1951 "Labels": ["latest-label"],
1952 }),
1953 );
1954 let resp = svc.label_parameter_version(&req).unwrap();
1955 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1956 assert_eq!(body["ParameterVersion"].as_i64().unwrap(), 2);
1957 }
1958
1959 #[test]
1960 fn label_parameter_version_invalid_labels() {
1961 let svc = make_service();
1962 put_param(&svc, "/label/invalid", "v1");
1963
1964 let req = make_request(
1966 "LabelParameterVersion",
1967 json!({
1968 "Name": "/label/invalid",
1969 "Labels": ["aws-reserved", "valid-label"],
1970 }),
1971 );
1972 let resp = svc.label_parameter_version(&req).unwrap();
1973 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
1974 let invalid = body["InvalidLabels"].as_array().unwrap();
1975 assert_eq!(invalid.len(), 1);
1976 assert_eq!(invalid[0].as_str().unwrap(), "aws-reserved");
1977 }
1978
1979 #[test]
1980 fn label_parameter_version_not_found() {
1981 let svc = make_service();
1982 put_param(&svc, "/label/notfound", "v1");
1983
1984 let req = make_request(
1985 "LabelParameterVersion",
1986 json!({
1987 "Name": "/label/notfound",
1988 "ParameterVersion": 999,
1989 "Labels": ["test"],
1990 }),
1991 );
1992 let result = svc.label_parameter_version(&req);
1993 assert!(result.is_err());
1994 }
1995
1996 #[test]
1997 fn unlabel_parameter_version_returns_invalid_for_missing_labels() {
1998 let svc = make_service();
1999 put_param(&svc, "/label/missing", "v1");
2000
2001 let req = make_request(
2002 "UnlabelParameterVersion",
2003 json!({
2004 "Name": "/label/missing",
2005 "ParameterVersion": 1,
2006 "Labels": ["nonexistent"],
2007 }),
2008 );
2009 let resp = svc.unlabel_parameter_version(&req).unwrap();
2010 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2011 let invalid = body["InvalidLabels"].as_array().unwrap();
2012 assert_eq!(invalid.len(), 1);
2013 assert_eq!(invalid[0].as_str().unwrap(), "nonexistent");
2014 }
2015
2016 #[test]
2017 fn unlabel_parameter_version_requires_parameter_version() {
2018 let svc = make_service();
2019 put_param(&svc, "/label/reqver", "v1");
2020
2021 let req = make_request(
2023 "UnlabelParameterVersion",
2024 json!({
2025 "Name": "/label/reqver",
2026 "Labels": ["some-label"],
2027 }),
2028 );
2029 let result = svc.unlabel_parameter_version(&req);
2030 assert!(result.is_err());
2031 }
2032
2033 fn create_doc(svc: &SsmService, name: &str) {
2036 let req = make_request(
2037 "CreateDocument",
2038 json!({
2039 "Name": name,
2040 "Content": r#"{"schemaVersion":"2.2","mainSteps":[]}"#,
2041 "DocumentType": "Command",
2042 }),
2043 );
2044 svc.create_document(&req).unwrap();
2045 }
2046
2047 #[test]
2048 fn update_document_and_default_version() {
2049 let svc = make_service();
2050 create_doc(&svc, "TestDoc");
2051
2052 let req = make_request(
2054 "UpdateDocument",
2055 json!({
2056 "Name": "TestDoc",
2057 "Content": r#"{"schemaVersion":"2.2","description":"v2","mainSteps":[]}"#,
2058 "VersionName": "release-2",
2059 }),
2060 );
2061 let resp = svc.update_document(&req).unwrap();
2062 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2063 let desc = &body["DocumentDescription"];
2064 assert_eq!(desc["DocumentVersion"].as_str().unwrap(), "2");
2065 assert_eq!(desc["VersionName"].as_str().unwrap(), "release-2");
2066
2067 let req = make_request("ListDocumentVersions", json!({ "Name": "TestDoc" }));
2069 let resp = svc.list_document_versions(&req).unwrap();
2070 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2071 assert_eq!(body["DocumentVersions"].as_array().unwrap().len(), 2);
2072
2073 let req = make_request(
2075 "UpdateDocumentDefaultVersion",
2076 json!({
2077 "Name": "TestDoc",
2078 "DocumentVersion": "2",
2079 }),
2080 );
2081 let resp = svc.update_document_default_version(&req).unwrap();
2082 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2083 assert_eq!(body["Description"]["DefaultVersion"].as_str().unwrap(), "2");
2084
2085 let req = make_request("DescribeDocument", json!({ "Name": "TestDoc" }));
2087 let resp = svc.describe_document(&req).unwrap();
2088 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2089 assert_eq!(body["Document"]["DefaultVersion"].as_str().unwrap(), "2");
2090 }
2091
2092 #[test]
2093 fn update_document_duplicate_content_fails() {
2094 let svc = make_service();
2095 create_doc(&svc, "DupDoc");
2096
2097 let req = make_request(
2099 "UpdateDocument",
2100 json!({
2101 "Name": "DupDoc",
2102 "Content": r#"{"schemaVersion":"2.2","mainSteps":[]}"#,
2103 }),
2104 );
2105 let result = svc.update_document(&req);
2106 assert!(result.is_err());
2107 }
2108
2109 #[test]
2110 fn document_permissions_modify_and_describe() {
2111 let svc = make_service();
2112 create_doc(&svc, "PermDoc");
2113
2114 let req = make_request(
2116 "ModifyDocumentPermission",
2117 json!({
2118 "Name": "PermDoc",
2119 "PermissionType": "Share",
2120 "AccountIdsToAdd": ["111111111111", "222222222222"],
2121 }),
2122 );
2123 svc.modify_document_permission(&req).unwrap();
2124
2125 let req = make_request(
2127 "DescribeDocumentPermission",
2128 json!({
2129 "Name": "PermDoc",
2130 "PermissionType": "Share",
2131 }),
2132 );
2133 let resp = svc.describe_document_permission(&req).unwrap();
2134 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2135 let ids = body["AccountIds"].as_array().unwrap();
2136 assert_eq!(ids.len(), 2);
2137
2138 let req = make_request(
2140 "ModifyDocumentPermission",
2141 json!({
2142 "Name": "PermDoc",
2143 "PermissionType": "Share",
2144 "AccountIdsToRemove": ["111111111111"],
2145 }),
2146 );
2147 svc.modify_document_permission(&req).unwrap();
2148
2149 let req = make_request(
2151 "DescribeDocumentPermission",
2152 json!({
2153 "Name": "PermDoc",
2154 "PermissionType": "Share",
2155 }),
2156 );
2157 let resp = svc.describe_document_permission(&req).unwrap();
2158 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2159 let ids = body["AccountIds"].as_array().unwrap();
2160 assert_eq!(ids.len(), 1);
2161 assert_eq!(ids[0].as_str().unwrap(), "222222222222");
2162 }
2163
2164 #[test]
2165 fn modify_document_permission_invalid_type() {
2166 let svc = make_service();
2167 create_doc(&svc, "PermDoc2");
2168
2169 let req = make_request(
2170 "ModifyDocumentPermission",
2171 json!({
2172 "Name": "PermDoc2",
2173 "PermissionType": "Invalid",
2174 "AccountIdsToAdd": ["111111111111"],
2175 }),
2176 );
2177 let result = svc.modify_document_permission(&req);
2178 assert!(result.is_err());
2179 }
2180
2181 #[test]
2184 fn describe_maintenance_window_targets_and_tasks() {
2185 let svc = make_service();
2186 let (window_id, _target_id, _task_id) = create_mw_with_target_and_task(&svc);
2187
2188 let req = make_request(
2190 "DescribeMaintenanceWindowTargets",
2191 json!({ "WindowId": window_id }),
2192 );
2193 let resp = svc.describe_maintenance_window_targets(&req).unwrap();
2194 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2195 let targets = body["Targets"].as_array().unwrap();
2196 assert_eq!(targets.len(), 1);
2197 assert_eq!(targets[0]["Name"].as_str().unwrap(), "test-target");
2198
2199 let req = make_request(
2201 "DescribeMaintenanceWindowTasks",
2202 json!({ "WindowId": window_id }),
2203 );
2204 let resp = svc.describe_maintenance_window_tasks(&req).unwrap();
2205 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2206 let tasks = body["Tasks"].as_array().unwrap();
2207 assert_eq!(tasks.len(), 1);
2208 assert_eq!(tasks[0]["TaskArn"].as_str().unwrap(), "AWS-RunShellScript");
2209 assert_eq!(tasks[0]["Name"].as_str().unwrap(), "test-task");
2210 }
2211
2212 fn create_baseline(svc: &SsmService, name: &str) -> String {
2215 let req = make_request(
2216 "CreatePatchBaseline",
2217 json!({
2218 "Name": name,
2219 "OperatingSystem": "AMAZON_LINUX_2",
2220 "Description": "test baseline",
2221 }),
2222 );
2223 let resp = svc.create_patch_baseline(&req).unwrap();
2224 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2225 body["BaselineId"].as_str().unwrap().to_string()
2226 }
2227
2228 #[test]
2229 fn patch_baseline_get_and_delete() {
2230 let svc = make_service();
2231 let baseline_id = create_baseline(&svc, "get-del-baseline");
2232
2233 let req = make_request("GetPatchBaseline", json!({ "BaselineId": baseline_id }));
2235 let resp = svc.get_patch_baseline(&req).unwrap();
2236 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2237 assert_eq!(body["Name"].as_str().unwrap(), "get-del-baseline");
2238 assert_eq!(body["OperatingSystem"].as_str().unwrap(), "AMAZON_LINUX_2");
2239 assert_eq!(body["Description"].as_str().unwrap(), "test baseline");
2240
2241 let req = make_request("DeletePatchBaseline", json!({ "BaselineId": baseline_id }));
2243 svc.delete_patch_baseline(&req).unwrap();
2244
2245 let req = make_request("GetPatchBaseline", json!({ "BaselineId": baseline_id }));
2247 let result = svc.get_patch_baseline(&req);
2248 assert!(result.is_err());
2249 }
2250
2251 #[test]
2252 fn describe_patch_baselines_with_filter() {
2253 let svc = make_service();
2254 create_baseline(&svc, "alpha-baseline");
2255 create_baseline(&svc, "beta-baseline");
2256
2257 let req = make_request(
2259 "DescribePatchBaselines",
2260 json!({
2261 "Filters": [{"Key": "NAME_PREFIX", "Values": ["alpha"]}],
2262 }),
2263 );
2264 let resp = svc.describe_patch_baselines(&req).unwrap();
2265 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2266 let baselines = body["BaselineIdentities"].as_array().unwrap();
2267 assert_eq!(baselines.len(), 1);
2268 assert_eq!(
2269 baselines[0]["BaselineName"].as_str().unwrap(),
2270 "alpha-baseline"
2271 );
2272 }
2273
2274 #[test]
2275 fn patch_group_register_and_deregister() {
2276 let svc = make_service();
2277 let baseline_id = create_baseline(&svc, "pg-baseline");
2278
2279 let req = make_request(
2281 "RegisterPatchBaselineForPatchGroup",
2282 json!({
2283 "BaselineId": baseline_id,
2284 "PatchGroup": "production",
2285 }),
2286 );
2287 let resp = svc.register_patch_baseline_for_patch_group(&req).unwrap();
2288 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2289 assert_eq!(body["PatchGroup"].as_str().unwrap(), "production");
2290
2291 let req = make_request(
2293 "GetPatchBaselineForPatchGroup",
2294 json!({
2295 "PatchGroup": "production",
2296 "OperatingSystem": "AMAZON_LINUX_2",
2297 }),
2298 );
2299 let resp = svc.get_patch_baseline_for_patch_group(&req).unwrap();
2300 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2301 assert_eq!(body["BaselineId"].as_str().unwrap(), baseline_id);
2302
2303 let req = make_request("DescribePatchGroups", json!({}));
2305 let resp = svc.describe_patch_groups(&req).unwrap();
2306 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2307 let mappings = body["Mappings"].as_array().unwrap();
2308 assert_eq!(mappings.len(), 1);
2309 assert_eq!(mappings[0]["PatchGroup"].as_str().unwrap(), "production");
2310
2311 let req = make_request(
2313 "DeregisterPatchBaselineForPatchGroup",
2314 json!({
2315 "BaselineId": baseline_id,
2316 "PatchGroup": "production",
2317 }),
2318 );
2319 svc.deregister_patch_baseline_for_patch_group(&req).unwrap();
2320
2321 let req = make_request("DescribePatchGroups", json!({}));
2323 let resp = svc.describe_patch_groups(&req).unwrap();
2324 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2325 assert!(body["Mappings"].as_array().unwrap().is_empty());
2326 }
2327
2328 #[test]
2329 fn delete_patch_baseline_removes_patch_groups() {
2330 let svc = make_service();
2331 let baseline_id = create_baseline(&svc, "del-pg-baseline");
2332
2333 let req = make_request(
2335 "RegisterPatchBaselineForPatchGroup",
2336 json!({
2337 "BaselineId": baseline_id,
2338 "PatchGroup": "staging",
2339 }),
2340 );
2341 svc.register_patch_baseline_for_patch_group(&req).unwrap();
2342
2343 let req = make_request("DeletePatchBaseline", json!({ "BaselineId": baseline_id }));
2345 svc.delete_patch_baseline(&req).unwrap();
2346
2347 let req = make_request("DescribePatchGroups", json!({}));
2349 let resp = svc.describe_patch_groups(&req).unwrap();
2350 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2351 assert!(body["Mappings"].as_array().unwrap().is_empty());
2352 }
2353
2354 #[test]
2357 fn get_command_invocation_success() {
2358 let svc = make_service();
2359 let cmd_id = send_command(&svc, "AWS-RunShellScript");
2360
2361 let req = make_request(
2362 "GetCommandInvocation",
2363 json!({
2364 "CommandId": cmd_id,
2365 "InstanceId": "i-1234567890abcdef0",
2366 }),
2367 );
2368 let resp = svc.get_command_invocation(&req).unwrap();
2369 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2370 assert_eq!(body["CommandId"].as_str().unwrap(), cmd_id);
2371 assert_eq!(body["InstanceId"].as_str().unwrap(), "i-1234567890abcdef0");
2372 assert_eq!(body["Status"].as_str().unwrap(), "Success");
2373 }
2374
2375 #[test]
2376 fn get_command_invocation_wrong_instance_fails() {
2377 let svc = make_service();
2378 let cmd_id = send_command(&svc, "AWS-RunShellScript");
2379
2380 let req = make_request(
2381 "GetCommandInvocation",
2382 json!({
2383 "CommandId": cmd_id,
2384 "InstanceId": "i-0000000000000000f",
2385 }),
2386 );
2387 let result = svc.get_command_invocation(&req);
2388 assert!(result.is_err());
2389 }
2390
2391 #[test]
2392 fn list_command_invocations() {
2393 let svc = make_service();
2394 let cmd_id = send_command(&svc, "AWS-RunShellScript");
2395
2396 let req = make_request("ListCommandInvocations", json!({}));
2398 let resp = svc.list_command_invocations(&req).unwrap();
2399 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2400 let invocations = body["CommandInvocations"].as_array().unwrap();
2401 assert!(!invocations.is_empty());
2402 assert_eq!(invocations[0]["CommandId"].as_str().unwrap(), cmd_id);
2403 assert_eq!(
2404 invocations[0]["InstanceId"].as_str().unwrap(),
2405 "i-1234567890abcdef0"
2406 );
2407
2408 let req = make_request("ListCommandInvocations", json!({ "CommandId": cmd_id }));
2410 let resp = svc.list_command_invocations(&req).unwrap();
2411 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2412 assert_eq!(body["CommandInvocations"].as_array().unwrap().len(), 1);
2413 }
2414}