Repository: getmoto/moto
Pull request #8966 (2025-06-10 20:37:08)

[Connect Campaigns] Added Tagging, List and other endpoints
To maintainers and reviewers, @bblommers and @bpandola. 
This PR adds some endpoints to the [Connect Campaigns](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns.html) service. When a campaign is created, it is usually in the `Initialized` state until is started. Other states include `Running, Paused, Stopped, Failed`. To check the state, the [get_campaign_state()](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns/client/get_campaign_state.html) is implemented. The tagging here supports both resource tagging api and campaign instance tagging. Calling the tagging methods modifies both the instance tags and the tags from the resource group tagging api.

Huge thanks 🙏  to @zkarpinski for pointing out my error with the` urls.py` file which caused some of my tests to fail in `SERVER_MODE`.

Error:  `"{0}/tags/(?P<arn>[^/]+)$": ConnectCampaignServiceResponse.dispatch,`
> The [^/]+ caused a forward slash to be added to the post request. This made tests pass when `SERVER_MODE` was set to false but failed when `SERVER_MODE` was set to true.

Solution:  `"{0}/tags/(?P<resourceArn>.+)$": ConnectCampaignServiceResponse.dispatch,`

```mermaid
sequenceDiagram
    participant User
    participant ConnectCampaigns
    participant Campaign

    User->>ConnectCampaigns: CreateCampaign()
    ConnectCampaigns-->>Campaign: State: Initialized

    User->>ConnectCampaigns: StartCampaign()
    ConnectCampaigns-->>Campaign: State: Running

    User->>ConnectCampaigns: PauseCampaign()
    ConnectCampaigns-->>Campaign: State: Paused

    User->>ConnectCampaigns: ResumeCampaign()
    ConnectCampaigns-->>Campaign: State: Running

    User->>ConnectCampaigns: StopCampaign()
    ConnectCampaigns-->>Campaign: State: Stopped

    alt Error during execution
        Campaign-->>Campaign: State: Failed
    end

    Note over User,Campaign: Failed state can be triggered by internal errors, not direct user action via specific API.
    Note over User,Campaign: There is no direct API to transition from Failed back to other states.
```

The following endpoints are implemented in this PR.

- [x] [get_campaign_state](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns/client/get_campaign_state.html)
- [x] [list_campaigns](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns/client/list_campaigns.html)
- [x] [start_campaign](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns/client/start_campaign.html)
- [x] [stop_campaign](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns/client/stop_campaign.html)
- [x] [pause_campaign](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns/client/pause_campaign.html)
- [x] [resume_campaign](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns/client/resume_campaign.html)
- [x] [tag_resource](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns/client/tag_resource.html)
- [x] [untag_resource](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns/client/untag_resource.html)
- [x] [list_tags_for_resource](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connectcampaigns/client/list_tags_for_resource.html)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8966?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
Attention: Patch coverage is `93.66197%` with `9 lines` in your changes missing coverage. Please review.
> Project coverage is 92.85%. Comparing base [(`cde945a`)](https://app.codecov.io/gh/getmoto/moto/commit/cde945aee9d322fb89fc1069fa9582eeb6a17fd5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`f787684`)](https://app.codecov.io/gh/getmoto/moto/commit/f787684bcadf8988d55223cc7e8fa14cb67190b3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 21 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/getmoto/moto/pull/8966?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Patch % | Lines |
|---|---|---|
| [moto/connectcampaigns/models.py](https://app.codecov.io/gh/getmoto/moto/pull/8966?src=pr&el=tree&filepath=moto%2Fconnectcampaigns%2Fmodels.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9jb25uZWN0Y2FtcGFpZ25zL21vZGVscy5weQ==) | 93.97% | [5 Missing :warning: ](https://app.codecov.io/gh/getmoto/moto/pull/8966?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) |
| [moto/resourcegroupstaggingapi/models.py](https://app.codecov.io/gh/getmoto/moto/pull/8966?src=pr&el=tree&filepath=moto%2Fresourcegroupstaggingapi%2Fmodels.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9yZXNvdXJjZWdyb3Vwc3RhZ2dpbmdhcGkvbW9kZWxzLnB5) | 71.42% | [4 Missing :warning: ](https://app.codecov.io/gh/getmoto/moto/pull/8966?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #8966      +/-   ##
==========================================
- Coverage   92.85%   92.85%   -0.01%     
==========================================
  Files        1287     1287              
  Lines      112812   112899      +87     
==========================================
+ Hits       104752   104832      +80     
- Misses       8060     8067       +7     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/8966/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/8966/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `28.53% <19.01%> (-0.01%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/8966/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `92.82% <93.66%> (-0.01%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/8966?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>

Patch:
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 6b6029df3..13e26d002 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -51,23 +51,20 @@
 
 ## amp
 <details>
-<summary>51% implemented</summary>
+<summary>58% implemented</summary>
 
 - [ ] create_alert_manager_definition
 - [X] create_logging_configuration
-- [ ] create_query_logging_configuration
 - [X] create_rule_groups_namespace
 - [ ] create_scraper
 - [X] create_workspace
 - [ ] delete_alert_manager_definition
 - [X] delete_logging_configuration
-- [ ] delete_query_logging_configuration
 - [X] delete_rule_groups_namespace
 - [ ] delete_scraper
 - [X] delete_workspace
 - [ ] describe_alert_manager_definition
 - [X] describe_logging_configuration
-- [ ] describe_query_logging_configuration
 - [X] describe_rule_groups_namespace
 - [ ] describe_scraper
 - [X] describe_workspace
@@ -82,7 +79,6 @@
 - [X] tag_resource
 - [X] untag_resource
 - [X] update_logging_configuration
-- [ ] update_query_logging_configuration
 - [ ] update_scraper
 - [X] update_workspace_alias
 - [ ] update_workspace_configuration
@@ -229,7 +225,7 @@
 
 ## apigatewayv2
 <details>
-<summary>70% implemented</summary>
+<summary>75% implemented</summary>
 
 - [X] create_api
 - [X] create_api_mapping
@@ -241,7 +237,6 @@
 - [X] create_model
 - [X] create_route
 - [X] create_route_response
-- [ ] create_routing_rule
 - [X] create_stage
 - [X] create_vpc_link
 - [ ] delete_access_log_settings
@@ -258,7 +253,6 @@
 - [X] delete_route_request_parameter
 - [X] delete_route_response
 - [ ] delete_route_settings
-- [ ] delete_routing_rule
 - [X] delete_stage
 - [X] delete_vpc_link
 - [ ] export_api
@@ -283,15 +277,12 @@
 - [X] get_route_response
 - [ ] get_route_responses
 - [X] get_routes
-- [ ] get_routing_rule
 - [X] get_stage
 - [X] get_stages
 - [X] get_tags
 - [X] get_vpc_link
 - [X] get_vpc_links
 - [ ] import_api
-- [ ] list_routing_rules
-- [ ] put_routing_rule
 - [X] reimport_api
 - [ ] reset_authorizers_cache
 - [X] tag_resource
@@ -964,7 +955,7 @@
 
 ## ce
 <details>
-<summary>17% implemented</summary>
+<summary>18% implemented</summary>
 
 - [ ] create_anomaly_monitor
 - [ ] create_anomaly_subscription
@@ -979,10 +970,8 @@
 - [ ] get_approximate_usage_records
 - [ ] get_commitment_purchase_analysis
 - [X] get_cost_and_usage
-- [ ] get_cost_and_usage_comparisons
 - [ ] get_cost_and_usage_with_resources
 - [ ] get_cost_categories
-- [ ] get_cost_comparison_drivers
 - [ ] get_cost_forecast
 - [ ] get_dimension_values
 - [ ] get_reservation_coverage
@@ -1359,7 +1348,7 @@
 
 ## cloudtrail
 <details>
-<summary>27% implemented</summary>
+<summary>28% implemented</summary>
 
 - [X] add_tags
 - [ ] cancel_query
@@ -1380,7 +1369,6 @@
 - [ ] generate_query
 - [ ] get_channel
 - [ ] get_dashboard
-- [ ] get_event_configuration
 - [ ] get_event_data_store
 - [X] get_event_selectors
 - [ ] get_import
@@ -1400,7 +1388,6 @@
 - [X] list_tags
 - [X] list_trails
 - [ ] lookup_events
-- [ ] put_event_configuration
 - [X] put_event_selectors
 - [X] put_insight_selectors
 - [ ] put_resource_policy
@@ -2064,27 +2051,27 @@
 
 ## connectcampaigns
 <details>
-<summary>22% implemented</summary>
+<summary>63% implemented</summary>
 
 - [X] create_campaign
 - [X] delete_campaign
 - [ ] delete_connect_instance_config
 - [ ] delete_instance_onboarding_job
 - [X] describe_campaign
-- [ ] get_campaign_state
+- [X] get_campaign_state
 - [ ] get_campaign_state_batch
 - [X] get_connect_instance_config
 - [ ] get_instance_onboarding_job_status
-- [ ] list_campaigns
-- [ ] list_tags_for_resource
-- [ ] pause_campaign
+- [X] list_campaigns
+- [X] list_tags_for_resource
+- [X] pause_campaign
 - [ ] put_dial_request_batch
-- [ ] resume_campaign
-- [ ] start_campaign
+- [X] resume_campaign
+- [X] start_campaign
 - [X] start_instance_onboarding_job
-- [ ] stop_campaign
-- [ ] tag_resource
-- [ ] untag_resource
+- [X] stop_campaign
+- [X] tag_resource
+- [X] untag_resource
 - [ ] update_campaign_dialer_config
 - [ ] update_campaign_name
 - [ ] update_campaign_outbound_call_config
@@ -3093,7 +3080,6 @@
 - [ ] export_image
 - [ ] export_transit_gateway_routes
 - [ ] export_verified_access_instance_client_configuration
-- [ ] get_active_vpn_tunnel_status
 - [ ] get_allowed_images_settings
 - [ ] get_associated_enclave_certificate_iam_roles
 - [ ] get_associated_ipv6_pool_cidrs
@@ -3192,7 +3178,6 @@
 - [X] modify_managed_prefix_list
 - [X] modify_network_interface_attribute
 - [ ] modify_private_dns_name_options
-- [ ] modify_public_ip_dns_name_options
 - [ ] modify_reserved_instances
 - [ ] modify_route_server
 - [X] modify_security_group_rules
@@ -6262,7 +6247,7 @@
 
 ## network-firewall
 <details>
-<summary>10% implemented</summary>
+<summary>11% implemented</summary>
 
 - [ ] associate_firewall_policy
 - [ ] associate_subnets
@@ -6270,15 +6255,12 @@
 - [ ] create_firewall_policy
 - [ ] create_rule_group
 - [ ] create_tls_inspection_configuration
-- [ ] create_vpc_endpoint_association
 - [ ] delete_firewall
 - [ ] delete_firewall_policy
 - [ ] delete_resource_policy
 - [ ] delete_rule_group
 - [ ] delete_tls_inspection_configuration
-- [ ] delete_vpc_endpoint_association
 - [X] describe_firewall
-- [ ] describe_firewall_metadata
 - [ ] describe_firewall_policy
 - [ ] describe_flow_operation
 - [X] describe_logging_configuration
@@ -6286,7 +6268,6 @@
 - [ ] describe_rule_group
 - [ ] describe_rule_group_metadata
 - [ ] describe_tls_inspection_configuration
-- [ ] describe_vpc_endpoint_association
 - [ ] disassociate_subnets
 - [ ] get_analysis_report_results
 - [ ] list_analysis_reports
@@ -6297,7 +6278,6 @@
 - [ ] list_rule_groups
 - [ ] list_tags_for_resource
 - [ ] list_tls_inspection_configurations
-- [ ] list_vpc_endpoint_associations
 - [ ] put_resource_policy
 - [ ] start_analysis_report
 - [ ] start_flow_capture
@@ -10214,7 +10194,6 @@
 - eks-auth
 - entityresolution
 - evidently
-- evs
 - finspace
 - finspace-data
 - fis
diff --git a/docs/docs/services/amp.rst b/docs/docs/services/amp.rst
index af7c00e82..471e3ec1a 100644
--- a/docs/docs/services/amp.rst
+++ b/docs/docs/services/amp.rst
@@ -18,7 +18,6 @@ amp
 
 - [ ] create_alert_manager_definition
 - [X] create_logging_configuration
-- [ ] create_query_logging_configuration
 - [X] create_rule_groups_namespace
   
         The ClientToken-parameter is not yet implemented
@@ -32,7 +31,6 @@ amp
 
 - [ ] delete_alert_manager_definition
 - [X] delete_logging_configuration
-- [ ] delete_query_logging_configuration
 - [X] delete_rule_groups_namespace
   
         The ClientToken-parameter is not yet implemented
@@ -46,7 +44,6 @@ amp
 
 - [ ] describe_alert_manager_definition
 - [X] describe_logging_configuration
-- [ ] describe_query_logging_configuration
 - [X] describe_rule_groups_namespace
 - [ ] describe_scraper
 - [X] describe_workspace
@@ -65,7 +62,6 @@ amp
 - [X] tag_resource
 - [X] untag_resource
 - [X] update_logging_configuration
-- [ ] update_query_logging_configuration
 - [ ] update_scraper
 - [X] update_workspace_alias
   
diff --git a/docs/docs/services/apigatewayv2.rst b/docs/docs/services/apigatewayv2.rst
index dc1eb4488..17e5bd765 100644
--- a/docs/docs/services/apigatewayv2.rst
+++ b/docs/docs/services/apigatewayv2.rst
@@ -35,7 +35,6 @@ apigatewayv2
         The following parameters are not yet implemented: ResponseModels, ResponseParameters
         
 
-- [ ] create_routing_rule
 - [X] create_stage
 - [X] create_vpc_link
 - [ ] delete_access_log_settings
@@ -52,7 +51,6 @@ apigatewayv2
 - [X] delete_route_request_parameter
 - [X] delete_route_response
 - [ ] delete_route_settings
-- [ ] delete_routing_rule
 - [X] delete_stage
 - [X] delete_vpc_link
 - [ ] export_api
@@ -93,15 +91,12 @@ apigatewayv2
         Pagination is not yet implemented
         
 
-- [ ] get_routing_rule
 - [X] get_stage
 - [X] get_stages
 - [X] get_tags
 - [X] get_vpc_link
 - [X] get_vpc_links
 - [ ] import_api
-- [ ] list_routing_rules
-- [ ] put_routing_rule
 - [X] reimport_api
   
         Only YAML is supported at the moment. Full OpenAPI-support is not guaranteed. Only limited validation is implemented
diff --git a/docs/docs/services/ce.rst b/docs/docs/services/ce.rst
index 7ee305e76..34a56e40b 100644
--- a/docs/docs/services/ce.rst
+++ b/docs/docs/services/ce.rst
@@ -84,10 +84,8 @@ ce
             resp = ce.get_cost_and_usage(...)
         
 
-- [ ] get_cost_and_usage_comparisons
 - [ ] get_cost_and_usage_with_resources
 - [ ] get_cost_categories
-- [ ] get_cost_comparison_drivers
 - [ ] get_cost_forecast
 - [ ] get_dimension_values
 - [ ] get_reservation_coverage
diff --git a/docs/docs/services/cloudtrail.rst b/docs/docs/services/cloudtrail.rst
index 298a434fa..28fc2b37f 100644
--- a/docs/docs/services/cloudtrail.rst
+++ b/docs/docs/services/cloudtrail.rst
@@ -35,7 +35,6 @@ cloudtrail
 - [ ] generate_query
 - [ ] get_channel
 - [ ] get_dashboard
-- [ ] get_event_configuration
 - [ ] get_event_data_store
 - [X] get_event_selectors
 - [ ] get_import
@@ -59,7 +58,6 @@ cloudtrail
 
 - [X] list_trails
 - [ ] lookup_events
-- [ ] put_event_configuration
 - [X] put_event_selectors
 - [X] put_insight_selectors
 - [ ] put_resource_policy
diff --git a/docs/docs/services/connectcampaigns.rst b/docs/docs/services/connectcampaigns.rst
index 3b10d80e7..3af635d7c 100644
--- a/docs/docs/services/connectcampaigns.rst
+++ b/docs/docs/services/connectcampaigns.rst
@@ -19,20 +19,20 @@ connectcampaigns
 - [ ] delete_connect_instance_config
 - [ ] delete_instance_onboarding_job
 - [X] describe_campaign
-- [ ] get_campaign_state
+- [X] get_campaign_state
 - [ ] get_campaign_state_batch
 - [X] get_connect_instance_config
 - [ ] get_instance_onboarding_job_status
-- [ ] list_campaigns
-- [ ] list_tags_for_resource
-- [ ] pause_campaign
+- [X] list_campaigns
+- [X] list_tags_for_resource
+- [X] pause_campaign
 - [ ] put_dial_request_batch
-- [ ] resume_campaign
-- [ ] start_campaign
+- [X] resume_campaign
+- [X] start_campaign
 - [X] start_instance_onboarding_job
-- [ ] stop_campaign
-- [ ] tag_resource
-- [ ] untag_resource
+- [X] stop_campaign
+- [X] tag_resource
+- [X] untag_resource
 - [ ] update_campaign_dialer_config
 - [ ] update_campaign_name
 - [ ] update_campaign_outbound_call_config
diff --git a/docs/docs/services/ec2.rst b/docs/docs/services/ec2.rst
index 3b9ecce82..88f6c6249 100644
--- a/docs/docs/services/ec2.rst
+++ b/docs/docs/services/ec2.rst
@@ -527,7 +527,6 @@ ec2
 - [ ] export_image
 - [ ] export_transit_gateway_routes
 - [ ] export_verified_access_instance_client_configuration
-- [ ] get_active_vpn_tunnel_status
 - [ ] get_allowed_images_settings
 - [ ] get_associated_enclave_certificate_iam_roles
 - [ ] get_associated_ipv6_pool_cidrs
@@ -626,7 +625,6 @@ ec2
 - [X] modify_managed_prefix_list
 - [X] modify_network_interface_attribute
 - [ ] modify_private_dns_name_options
-- [ ] modify_public_ip_dns_name_options
 - [ ] modify_reserved_instances
 - [ ] modify_route_server
 - [X] modify_security_group_rules
diff --git a/docs/docs/services/network-firewall.rst b/docs/docs/services/network-firewall.rst
index 34467b558..0895bedcd 100644
--- a/docs/docs/services/network-firewall.rst
+++ b/docs/docs/services/network-firewall.rst
@@ -22,15 +22,12 @@ network-firewall
 - [ ] create_firewall_policy
 - [ ] create_rule_group
 - [ ] create_tls_inspection_configuration
-- [ ] create_vpc_endpoint_association
 - [ ] delete_firewall
 - [ ] delete_firewall_policy
 - [ ] delete_resource_policy
 - [ ] delete_rule_group
 - [ ] delete_tls_inspection_configuration
-- [ ] delete_vpc_endpoint_association
 - [X] describe_firewall
-- [ ] describe_firewall_metadata
 - [ ] describe_firewall_policy
 - [ ] describe_flow_operation
 - [X] describe_logging_configuration
@@ -38,7 +35,6 @@ network-firewall
 - [ ] describe_rule_group
 - [ ] describe_rule_group_metadata
 - [ ] describe_tls_inspection_configuration
-- [ ] describe_vpc_endpoint_association
 - [ ] disassociate_subnets
 - [ ] get_analysis_report_results
 - [ ] list_analysis_reports
@@ -49,7 +45,6 @@ network-firewall
 - [ ] list_rule_groups
 - [ ] list_tags_for_resource
 - [ ] list_tls_inspection_configurations
-- [ ] list_vpc_endpoint_associations
 - [ ] put_resource_policy
 - [ ] start_analysis_report
 - [ ] start_flow_capture
diff --git a/docs/docs/services/ram.rst b/docs/docs/services/ram.rst
index 546f9704d..1a3b86074 100644
--- a/docs/docs/services/ram.rst
+++ b/docs/docs/services/ram.rst
@@ -38,7 +38,7 @@ ram
 - [ ] list_principals
 - [ ] list_replace_permission_associations_work
 - [ ] list_resource_share_permissions
-- [ ] list_resource_types
+- [X] list_resource_types
 - [ ] list_resources
 - [ ] promote_permission_created_from_policy
 - [ ] promote_resource_share_created_from_policy
diff --git a/moto/athena/models.py b/moto/athena/models.py
index a8fcdbcdc..152e692e4 100644
--- a/moto/athena/models.py
+++ b/moto/athena/models.py
@@ -117,6 +117,7 @@ class Execution(BaseModel):
         self.workgroup = workgroup
         self.execution_parameters = execution_parameters
         self.start_time = time.time()
+        self.end_time = time.time()
         self.status = "SUCCEEDED"
 
         if self.config is not None and "OutputLocation" in self.config:
diff --git a/moto/athena/responses.py b/moto/athena/responses.py
index 60cd55526..0b58b21b2 100644
--- a/moto/athena/responses.py
+++ b/moto/athena/responses.py
@@ -76,18 +76,24 @@ class AthenaResponse(BaseResponse):
                 "Query": execution.query,
                 "StatementType": statement_type,
                 "ResultConfiguration": execution.config,
+                "ResultReuseConfiguration": {
+                    "ResultReuseByAgeConfiguration": {"Enabled": False}
+                },
                 "QueryExecutionContext": execution.context,
                 "Status": {
                     "State": execution.status,
                     "SubmissionDateTime": execution.start_time,
+                    "CompletionDateTime": execution.end_time,
                 },
                 "Statistics": {
                     "EngineExecutionTimeInMillis": 0,
                     "DataScannedInBytes": 0,
                     "TotalExecutionTimeInMillis": 0,
                     "QueryQueueTimeInMillis": 0,
+                    "ServicePreProcessingTimeInMillis": 0,
                     "QueryPlanningTimeInMillis": 0,
                     "ServiceProcessingTimeInMillis": 0,
+                    "ResultReuseInformation": {"ReusedPreviousResult": False},
                 },
                 "WorkGroup": execution.workgroup.name if execution.workgroup else None,
             }
diff --git a/moto/backends.py b/moto/backends.py
index 6023d9b0e..bb92611bb 100644
--- a/moto/backends.py
+++ b/moto/backends.py
@@ -38,6 +38,7 @@ if TYPE_CHECKING:
     from moto.cognitoidp.models import CognitoIdpBackend
     from moto.comprehend.models import ComprehendBackend
     from moto.config.models import ConfigBackend
+    from moto.connectcampaigns.models import ConnectCampaignServiceBackend
     from moto.core.base_backend import SERVICE_BACKEND, BackendDict
     from moto.databrew.models import DataBrewBackend
     from moto.datapipeline.models import DataPipelineBackend
@@ -228,6 +229,7 @@ SERVICE_NAMES = Union[
     "Literal['cognito-identity']",
     "Literal['cognito-idp']",
     "Literal['comprehend']",
+    "Literal['connectcampaigns']",
     "Literal['config']",
     "Literal['databrew']",
     "Literal['datapipeline']",
@@ -442,6 +444,10 @@ def get_backend(name: "Literal['cognito-idp']") -> "BackendDict[CognitoIdpBacken
 @overload
 def get_backend(name: "Literal['comprehend']") -> "BackendDict[ComprehendBackend]": ...
 @overload
+def get_backend(
+    name: "Literal['connectcampaigns']",
+) -> "BackendDict[ConnectCampaignServiceBackend]": ...
+@overload
 def get_backend(name: "Literal['config']") -> "BackendDict[ConfigBackend]": ...
 @overload
 def get_backend(name: "Literal['databrew']") -> "BackendDict[DataBrewBackend]": ...
diff --git a/moto/connectcampaigns/models.py b/moto/connectcampaigns/models.py
index 3dc5c29c5..24068bb38 100644
--- a/moto/connectcampaigns/models.py
+++ b/moto/connectcampaigns/models.py
@@ -1,14 +1,27 @@
 """ConnectCampaignServiceBackend class with methods for supported APIs."""
 
 import uuid
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, Dict, List, Optional, Tuple
+from urllib.parse import unquote
 
 from moto.core import DEFAULT_ACCOUNT_ID
 from moto.core.base_backend import BackendDict, BaseBackend
 from moto.core.common_models import BaseModel
+from moto.utilities.paginator import paginate
+from moto.utilities.tagging_service import TaggingService
 
 from .exceptions import ResourceNotFoundException, ValidationException
 
+PAGINATION_MODEL = {
+    "list_campaigns": {
+        "input_token": "next_token",
+        "limit_key": "max_results",
+        "limit_default": 100,
+        "output_token": "nextToken",
+        "unique_attribute": "id",
+    }
+}
+
 
 class ConnectCampaign(BaseModel):
     def __init__(
@@ -23,6 +36,7 @@ class ConnectCampaign(BaseModel):
         self.id = str(uuid.uuid4())
         self.name = name
         self.connect_instance_id = connect_instance_id
+        self.state = "Initialized"
         self.dialer_config = dialer_config
         self.outbound_call_config = outbound_call_config
         self.region = region
@@ -93,6 +107,7 @@ class ConnectCampaignServiceBackend(BaseBackend):
         self.campaigns: Dict[str, ConnectCampaign] = {}
         self.instance_configs: Dict[str, ConnectInstanceConfig] = {}
         self.onboarding_jobs: Dict[str, ConnectInstanceOnboardingJobStatus] = {}
+        self.tagger = TaggingService()
 
     def create_campaign(
         self,
@@ -183,6 +198,126 @@ class ConnectCampaignServiceBackend(BaseBackend):
 
         return job_status.to_dict()
 
+    def start_campaign(self, id: str) -> None:
+        if id not in self.campaigns:
+            raise ResourceNotFoundException(f"Campaign with id {id} not found")
+        self.campaigns[id].state = "Running"
+        return
+
+    def stop_campaign(self, id: str) -> None:
+        if id not in self.campaigns:
+            raise ResourceNotFoundException(f"Campaign with id {id} not found")
+        self.campaigns[id].state = "Stopped"
+        return
+
+    def pause_campaign(self, id: str) -> None:
+        if id not in self.campaigns:
+            raise ResourceNotFoundException(f"Campaign with id {id} not found")
+        self.campaigns[id].state = "Paused"
+        return
+
+    def resume_campaign(self, id: str) -> None:
+        if id not in self.campaigns:
+            raise ResourceNotFoundException(f"Campaign with id {id} not found")
+        self.campaigns[id].state = "Running"
+        return
+
+    def get_campaign_state(self, id: str) -> str:
+        if id not in self.campaigns:
+            raise ResourceNotFoundException(f"Campaign with id {id} not found")
+        return self.campaigns[id].state
+
+    @paginate(pagination_model=PAGINATION_MODEL)
+    def list_campaigns(
+        self,
+        filters: Optional[Dict[str, Dict[str, str]]],
+        max_results: Optional[int],
+        next_token: Optional[str],
+    ) -> List[Dict[str, str]]:
+        filtered_campaigns = list(self.campaigns.values())
+
+        if filters and "instanceIdFilter" in filters:
+            filter_value = filters["instanceIdFilter"]["value"]
+            filter_operator = filters["instanceIdFilter"]["operator"]
+
+            operator_logic = {
+                "Eq": lambda campaign: campaign.connect_instance_id == filter_value,
+                "Ne": lambda campaign: campaign.connect_instance_id != filter_value,
+                "Contains": lambda campaign: filter_value
+                in campaign.connect_instance_id,
+                "StartsWith": lambda campaign: campaign.connect_instance_id.startswith(
+                    filter_value
+                ),
+            }
+
+            if filter_operator not in operator_logic:
+                raise ValidationException(f"Unsupported operator: {filter_operator}")
+
+            filtered_campaigns = list(
+                filter(operator_logic[filter_operator], filtered_campaigns)
+            )
+
+        campaign_summary_list = [
+            {
+                "id": campaign.id,
+                "arn": campaign.arn,
+                "name": campaign.name,
+                "connectInstanceId": campaign.connect_instance_id,
+            }
+            for campaign in filtered_campaigns
+        ]
+        return campaign_summary_list
+
+    def tag_resource(self, arn: str, tags: Dict[str, str]) -> None:
+        arn = unquote(arn)
+        campaign = None
+        for c in self.campaigns.values():
+            if c.arn == arn:
+                campaign = c
+                break
+
+        if campaign is None:
+            raise ResourceNotFoundException(f"Resource {arn} not found")
+
+        tag_list = [{"Key": k, "Value": v} for k, v in tags.items()]
+        campaign.tags.update(tags)
+        self.tagger.tag_resource(arn, tag_list)
+        return
+
+    def untag_resource(self, arn: str, tag_keys: List[str]) -> None:
+        arn = unquote(arn)
+        campaign = None
+        for c in self.campaigns.values():
+            if c.arn == arn:
+                campaign = c
+                break
+
+        if campaign is None:
+            raise ResourceNotFoundException(f"Resource {arn} not found")
+
+        if not isinstance(tag_keys, list):
+            tag_keys = [tag_keys]
+        if not tag_keys:
+            raise ValidationException("tagKeys is a required parameter")
+
+        for tag in tag_keys:
+            campaign.tags.pop(tag, None)
+        self.tagger.untag_resource_using_names(arn, tag_keys)
+        return
+
+    def list_tags_for_resource(self, arn: str) -> Dict[str, str]:
+        arn = unquote(arn)
+        campaign = None
+        for c in self.campaigns.values():
+            if c.arn == arn:
+                campaign = c
+                break
+
+        if campaign is None:
+            raise ResourceNotFoundException(f"Resource {arn} not found")
+
+        return self.tagger.get_tag_dict_for_resource(arn)
+
 
 connectcampaigns_backends = BackendDict(
     ConnectCampaignServiceBackend, "connectcampaigns"
diff --git a/moto/connectcampaigns/responses.py b/moto/connectcampaigns/responses.py
index 91bdc0336..88109727d 100644
--- a/moto/connectcampaigns/responses.py
+++ b/moto/connectcampaigns/responses.py
@@ -1,6 +1,7 @@
 """Handles incoming connectcampaigns requests, invokes methods, returns responses."""
 
 import json
+from typing import Dict, List, Optional, Tuple
 
 from moto.core.responses import BaseResponse
 
@@ -85,3 +86,94 @@ class ConnectCampaignServiceResponse(BaseResponse):
         response = {"connectInstanceOnboardingJobStatus": job_status}
 
         return json.dumps(response)
+
+    def start_campaign(self) -> str:
+        id = self.path.split("/")[2]
+
+        self.connectcampaigns_backend.start_campaign(
+            id=id,
+        )
+
+        return "{}"
+
+    def stop_campaign(self) -> str:
+        id = self.path.split("/")[2]
+
+        self.connectcampaigns_backend.stop_campaign(
+            id=id,
+        )
+
+        return "{}"
+
+    def pause_campaign(self) -> str:
+        id = self.path.split("/")[2]
+
+        self.connectcampaigns_backend.pause_campaign(
+            id=id,
+        )
+
+        return "{}"
+
+    def resume_campaign(self) -> str:
+        id = self.path.split("/")[2]
+
+        self.connectcampaigns_backend.resume_campaign(
+            id=id,
+        )
+
+        return "{}"
+
+    def get_campaign_state(self) -> str:
+        id = self.path.split("/")[2]
+
+        campaign_state = self.connectcampaigns_backend.get_campaign_state(
+            id=id,
+        )
+
+        response = {"state": campaign_state}
+
+        return json.dumps(response)
+
+    def list_campaigns(self) -> str:
+        max_results = self._get_param("maxResults")
+        next_token = self._get_param("nextToken")
+        filters = self._get_param("filters", {})
+
+        campaigns_result: Tuple[List[Dict[str, str]], Optional[str]] = (
+            self.connectcampaigns_backend.list_campaigns(
+                filters=filters,
+                max_results=max_results,
+                next_token=next_token,
+            )
+        )
+
+        response = {
+            "campaignSummaryList": campaigns_result[0],
+            "nextToken": campaigns_result[1],
+        }
+        return json.dumps(response)
+
+    def tag_resource(self) -> str:
+        arn = self._get_param("arn")
+        tags = self._get_param("tags")
+        self.connectcampaigns_backend.tag_resource(
+            arn=arn,
+            tags=tags,
+        )
+        return json.dumps(dict())
+
+    def untag_resource(self) -> str:
+        arn = self._get_param("arn")
+        tag_keys = self._get_param("tagKeys")
+        self.connectcampaigns_backend.untag_resource(
+            arn=arn,
+            tag_keys=tag_keys,
+        )
+        return json.dumps(dict())
+
+    def list_tags_for_resource(self) -> str:
+        arn = self._get_param("arn")
+        tags = self.connectcampaigns_backend.list_tags_for_resource(
+            arn=arn,
+        )
+        return json.dumps(dict(tags=tags))
diff --git a/moto/connectcampaigns/urls.py b/moto/connectcampaigns/urls.py
index fd20423f7..2d3759ed1 100644
--- a/moto/connectcampaigns/urls.py
+++ b/moto/connectcampaigns/urls.py
@@ -7,8 +7,16 @@ url_bases = [
 ]
 
 url_paths = {
+    "{0}/tags/(?P<arn>.+)$": ConnectCampaignServiceResponse.dispatch,
+    "{0}/tags/(?P<arn>[^/]+)?tagKeys=(?P<tagKeys>[^/]+)$": ConnectCampaignServiceResponse.dispatch,
     "{0}/campaigns$": ConnectCampaignServiceResponse.dispatch,
+    "{0}/campaigns-summary$": ConnectCampaignServiceResponse.dispatch,
     "{0}/campaigns/(?P<id>[^/]+)$": ConnectCampaignServiceResponse.dispatch,
+    "{0}/campaigns/(?P<id>[^/]+)/start$": ConnectCampaignServiceResponse.dispatch,
+    "{0}/campaigns/(?P<id>[^/]+)/stop$": ConnectCampaignServiceResponse.dispatch,
+    "{0}/campaigns/(?P<id>[^/]+)/pause$": ConnectCampaignServiceResponse.dispatch,
+    "{0}/campaigns/(?P<id>[^/]+)/resume$": ConnectCampaignServiceResponse.dispatch,
+    "{0}/campaigns/(?P<id>[^/]+)/state$": ConnectCampaignServiceResponse.dispatch,
     "{0}/connect-instance/(?P<connectInstanceId>[^/]+)/config$": ConnectCampaignServiceResponse.dispatch,
     "{0}/connect-instance/(?P<connectInstanceId>[^/]+)/onboarding$": ConnectCampaignServiceResponse.dispatch,
 }
diff --git a/moto/ram/models.py b/moto/ram/models.py
index 69fb55375..6f19c4c2c 100644
--- a/moto/ram/models.py
+++ b/moto/ram/models.py
@@ -37,7 +37,10 @@ class ResourceShare(BaseModel):
         "report-group",  # AWS CodeBuild report group
         "resolver-rule",  # Amazon Route 53 forwarding rule
         "subnet",  # Amazon EC2 subnet
-        "transit-gateway",  # Amazon EC2 transit gateway
+        "transit-gateway",  # Amazon EC2 transit gateway,
+        "database",  # Amazon Glue database
+        "table",  # Amazon Glue table
+        "catalog",  # Amazon Glue catalog
     ]
 
     def __init__(self, account_id: str, region: str, **kwargs: Any):
@@ -153,6 +156,94 @@ class ResourceShare(BaseModel):
 
 
 class ResourceAccessManagerBackend(BaseBackend):
+    RESOURCE_TYPES = [  # List of resource types based on SHAREABLE_RESOURCES
+        {
+            "resourceType": "rds:Cluster",
+            "serviceName": "rds",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "imagebuilder:Component",
+            "serviceName": "imagebuilder",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "networkmanager:CoreNetwork",
+            "serviceName": "networkmanager",
+            "resourceRegionScope": "GLOBAL",
+        },
+        {
+            "resourceType": "resource-groups:Group",
+            "serviceName": "resource-groups",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "imagebuilder:Image",
+            "serviceName": "imagebuilder",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "imagebuilder:ImageRecipe",
+            "serviceName": "imagebuilder",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "license-manager:LicenseConfiguration",
+            "serviceName": "license-manager",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "appmesh:Mesh",
+            "serviceName": "appmesh",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "ec2:PrefixList",
+            "serviceName": "ec2",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "codebuild:Project",
+            "serviceName": "codebuild",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "codebuild:ReportGroup",
+            "serviceName": "codebuild",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "route53resolver:ResolverRule",
+            "serviceName": "route53resolver",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "ec2:Subnet",
+            "serviceName": "ec2",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "ec2:TransitGatewayMulticastDomain",
+            "serviceName": "ec2",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "glue:Database",
+            "serviceName": "glue",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "glue:Table",
+            "serviceName": "glue",
+            "resourceRegionScope": "REGIONAL",
+        },
+        {
+            "resourceType": "glue:Catalog",
+            "serviceName": "glue",
+            "resourceRegionScope": "REGIONAL",
+        },
+    ]
+
     def __init__(self, region_name: str, account_id: str):
         super().__init__(region_name, account_id)
         self.resource_shares: List[ResourceShare] = []
@@ -302,5 +393,25 @@ class ResourceAccessManagerBackend(BaseBackend):
 
         return dict(resourceShareAssociations=associations)
 
+    def list_resource_types(self, **kwargs: Any) -> Dict[str, Any]:
+        resource_region_scope = kwargs.get("resourceRegionScope", "ALL")
+
+        if resource_region_scope not in ["ALL", "REGIONAL", "GLOBAL"]:
+            raise InvalidParameterException(
+                f"{resource_region_scope} is not a valid resource region "
+                "scope value. Specify a valid value and try again."
+            )
+
+        if resource_region_scope == "ALL":
+            resource_types = self.RESOURCE_TYPES
+        else:
+            resource_types = [
+                resource_type
+                for resource_type in self.RESOURCE_TYPES
+                if resource_type["resourceRegionScope"] == resource_region_scope
+            ]
+
+        return dict(resourceTypes=resource_types)
+
 
 ram_backends = BackendDict(ResourceAccessManagerBackend, "ram")
diff --git a/moto/ram/responses.py b/moto/ram/responses.py
index 93ea41153..7b36f71f1 100644
--- a/moto/ram/responses.py
+++ b/moto/ram/responses.py
@@ -41,3 +41,6 @@ class ResourceAccessManagerResponse(BaseResponse):
         return json.dumps(
             self.ram_backend.get_resource_share_associations(**self.request_params)
         )
+
+    def list_resource_types(self) -> str:
+        return json.dumps(self.ram_backend.list_resource_types(**self.request_params))
diff --git a/moto/ram/urls.py b/moto/ram/urls.py
index 1b5775bb0..e973085d1 100644
--- a/moto/ram/urls.py
+++ b/moto/ram/urls.py
@@ -9,4 +9,5 @@ url_paths = {
     "{0}/getresourceshares$": ResourceAccessManagerResponse.dispatch,
     "{0}/getresourceshareassociations$": ResourceAccessManagerResponse.dispatch,
     "{0}/updateresourceshare$": ResourceAccessManagerResponse.dispatch,
+    "{0}/listresourcetypes$": ResourceAccessManagerResponse.dispatch,
 }
diff --git a/moto/rds/models.py b/moto/rds/models.py
index 49304d78e..cb4f97375 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -492,6 +492,7 @@ class DBCluster(RDSBaseModel):
         port: Optional[int] = None,
         preferred_backup_window: str = "01:37-02:07",
         preferred_maintenance_window: str = "wed:02:40-wed:03:10",
+        publicly_accessible: bool = False,
         storage_encrypted: bool = False,
         tags: Optional[List[Dict[str, str]]] = None,
         vpc_security_group_ids: Optional[List[str]] = None,
@@ -582,6 +583,7 @@ class DBCluster(RDSBaseModel):
         self.port = port or DBCluster.default_port(self.engine)
         self.preferred_backup_window = preferred_backup_window
         self.preferred_maintenance_window = preferred_maintenance_window
+        self.publicly_accessible = publicly_accessible
         # This should default to the default security group
         self._vpc_security_group_ids = vpc_security_group_ids or []
         self.hosted_zone_id = "".join(
diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py
index 136fb0dea..0de8b6697 100644
--- a/moto/resourcegroupstaggingapi/models.py
+++ b/moto/resourcegroupstaggingapi/models.py
@@ -7,6 +7,10 @@ from moto.backup.models import BackupBackend, backup_backends
 from moto.clouddirectory import CloudDirectoryBackend, clouddirectory_backends
 from moto.cloudfront.models import CloudFrontBackend, cloudfront_backends
 from moto.cloudwatch.models import CloudWatchBackend, cloudwatch_backends
+from moto.connectcampaigns.models import (
+    ConnectCampaignServiceBackend,
+    connectcampaigns_backends,
+)
 from moto.core.base_backend import BackendDict, BaseBackend
 from moto.core.exceptions import RESTError
 from moto.dms.models import DatabaseMigrationServiceBackend, dms_backends
@@ -210,6 +214,13 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
     def cloudwatch_backend(self) -> CloudWatchBackend:
         return cloudwatch_backends[self.account_id][self.region_name]
 
+    @property
+    def connectcampaigns_backend(self) -> Optional[ConnectCampaignServiceBackend]:
+        # Connect Campaigns service has limited region availability
+        if self.region_name in connectcampaigns_backends[self.account_id].regions:
+            return connectcampaigns_backends[self.account_id][self.region_name]
+        return None
+
     def _get_resources_generator(
         self,
         tag_filters: Optional[List[Dict[str, Any]]] = None,
@@ -388,6 +399,20 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
                             "Tags": tags,
                         }
 
+        # Connect Campaigns v1
+        if self.connectcampaigns_backend:
+            if not resource_type_filters or "connectcampaigns" in resource_type_filters:
+                connectcampaigns_backend: ConnectCampaignServiceBackend = (
+                    connectcampaigns_backends[self.account_id][self.region_name]
+                )
+                for campaign in connectcampaigns_backend.campaigns.values():
+                    tags = connectcampaigns_backend.tagger.list_tags_for_resource(
+                        campaign.arn
+                    )["Tags"]
+                    if not tags or not tag_filter(tags):
+                        continue
+                    yield {"ResourceARN": f"{campaign.arn}", "Tags": tags}
+
         # DMS
         if not resource_type_filters or "dms:endpoint" in resource_type_filters:
             for endpoint in self.dms_backend.endpoints.values():


Test patch:
diff --git a/tests/test_athena/test_athena.py b/tests/test_athena/test_athena.py
index 6229dec8a..0121a7a87 100644
--- a/tests/test_athena/test_athena.py
+++ b/tests/test_athena/test_athena.py
@@ -1,3 +1,5 @@
+from datetime import datetime
+
 import boto3
 import pytest
 from botocore.exceptions import ClientError
@@ -172,7 +174,7 @@ def test_get_query_execution(location):
     # Start Query
     exex_id = client.start_query_execution(
         QueryString=query,
-        QueryExecutionContext={"Database": database},
+        QueryExecutionContext={"Database": database, "Catalog": "awsdatacatalog"},
         ResultConfiguration={"OutputLocation": location},
     )["QueryExecutionId"]
     #
@@ -186,15 +188,24 @@ def test_get_query_execution(location):
         assert result_config["OutputLocation"] == f"{location}{exex_id}.csv"
     else:
         assert result_config["OutputLocation"] == f"{location}/{exex_id}.csv"
+    assert (
+        details["ResultReuseConfiguration"]["ResultReuseByAgeConfiguration"]["Enabled"]
+        is False
+    )
     assert details["QueryExecutionContext"]["Database"] == database
+    assert details["QueryExecutionContext"]["Catalog"] == "awsdatacatalog"
     assert details["Status"]["State"] == "SUCCEEDED"
+    assert isinstance(details["Status"]["SubmissionDateTime"], datetime)
+    assert isinstance(details["Status"]["CompletionDateTime"], datetime)
     assert details["Statistics"] == {
         "EngineExecutionTimeInMillis": 0,
         "DataScannedInBytes": 0,
         "TotalExecutionTimeInMillis": 0,
         "QueryQueueTimeInMillis": 0,
+        "ServicePreProcessingTimeInMillis": 0,
         "QueryPlanningTimeInMillis": 0,
         "ServiceProcessingTimeInMillis": 0,
+        "ResultReuseInformation": {"ReusedPreviousResult": False},
     }
     assert "WorkGroup" not in details
 
diff --git a/tests/test_connectcampaigns/test_connectcampaigns.py b/tests/test_connectcampaigns/test_connectcampaigns.py
index 21da781db..3f195e978 100644
--- a/tests/test_connectcampaigns/test_connectcampaigns.py
+++ b/tests/test_connectcampaigns/test_connectcampaigns.py
@@ -175,3 +175,395 @@ def test_start_instance_onboarding_job():
             connectInstanceId=connect_instance_id,
             encryptionConfig={"enabled": True, "encryptionType": "KMS"},
         )
+
+
+@mock_aws
+def test_start_campaign():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    create_response = client.create_campaign(
+        name="CampaignToStart",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+            "connectSourcePhoneNumber": "+12065550100",
+            "connectQueueId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    campaign_id = create_response["id"]
+
+    client.start_campaign(id=campaign_id)
+    describe_response = client.get_campaign_state(id=campaign_id)
+
+    assert describe_response["state"] == "Running"
+
+
+@mock_aws
+def test_stop_campaign():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    create_response = client.create_campaign(
+        name="CampaignToStop",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+            "connectSourcePhoneNumber": "+12065550100",
+            "connectQueueId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    campaign_id = create_response["id"]
+
+    client.stop_campaign(id=campaign_id)
+    describe_response = client.get_campaign_state(id=campaign_id)
+
+    assert describe_response["state"] == "Stopped"
+
+
+@mock_aws
+def test_list_campaigns():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    # Create a couple of campaigns
+    client.create_campaign(
+        name="Campaign1",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    client.create_campaign(
+        name="Campaign2",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    response = client.list_campaigns()
+
+    assert len(response["campaignSummaryList"]) >= 2
+    assert any(c["name"] == "Campaign1" for c in response["campaignSummaryList"])
+    assert any(c["name"] == "Campaign2" for c in response["campaignSummaryList"])
+
+
+@mock_aws
+def test_list_campaigns_with_filters():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    # Create a couple of campaigns
+    client.create_campaign(
+        name="Campaign1",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    client.create_campaign(
+        name="Campaign2",
+        connectInstanceId="12345678-1234-1234-1234-000000000000",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    # Filter by name
+    response = client.list_campaigns(
+        filters={
+            "instanceIdFilter": {
+                "value": "12345678-1234-1234-1234-123456789012",
+                "operator": "Eq",
+            }
+        }
+    )
+
+    assert len(response["campaignSummaryList"]) == 1
+    assert response["campaignSummaryList"][0]["name"] == "Campaign1"
+
+    # Filter by connectInstanceId
+    response = client.list_campaigns(
+        filters={
+            "instanceIdFilter": {
+                "value": "12345678-1234-1234-1234-12340000012",
+                "operator": "Eq",
+            }
+        }
+    )
+    assert len(response["campaignSummaryList"]) == 0
+
+
+@mock_aws
+def test_start_campaign_invalid_id():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    with pytest.raises(client.exceptions.ResourceNotFoundException):
+        client.start_campaign(id="non-existent-id")
+
+
+@mock_aws
+def test_stop_campaign_invalid_id():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    with pytest.raises(client.exceptions.ResourceNotFoundException):
+        client.stop_campaign(id="non-existent-id")
+
+
+@mock_aws
+def test_tag_resource():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    create_response = client.create_campaign(
+        name="CampaignToTag",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+        },
+        tags={"Environment": "TestLater", "Owner": "DevTeamLater"},
+    )
+
+    client.tag_resource(
+        arn=create_response["arn"],
+        tags={"Environment": "Test", "Owner": "DevTeam"},
+    )
+
+    # assert tag_response is None
+    # Verify tags
+    describe_tags = client.list_tags_for_resource(arn=create_response["arn"])
+    assert "tags" in describe_tags
+
+    tags = describe_tags["tags"]
+    assert tags["Environment"] == "Test"
+    assert tags["Owner"] == "DevTeam"
+
+
+@mock_aws
+def test_untag_resource():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    create_response = client.create_campaign(
+        name="CampaignToUntag",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    campaign_id = create_response["id"]
+    client.tag_resource(
+        arn=create_response["arn"],
+        tags={"Environment": "Test", "Owner": "DevTeam"},
+    )
+
+    client.untag_resource(
+        arn=create_response["arn"],
+        tagKeys=["Environment"],
+    )
+    # Verify tags after untagging
+    describe_response = client.describe_campaign(id=campaign_id)
+    assert "tags" in describe_response["campaign"]
+    tags = describe_response["campaign"]["tags"]
+    assert "Environment" not in tags
+    assert tags["Owner"] == "DevTeam"
+
+
+@mock_aws
+def test_list_tags_for_resource():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    create_response = client.create_campaign(
+        name="CampaignToListTags",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    client.tag_resource(
+        arn=create_response["arn"],
+        tags={"Environment": "Test", "Owner": "DevTeam"},
+    )
+
+    tags_response = client.list_tags_for_resource(arn=create_response["arn"])
+
+    assert len(tags_response) == 2
+    assert tags_response["tags"]["Environment"] == "Test"
+    assert tags_response["tags"]["Owner"] == "DevTeam"
+
+
+@mock_aws
+def test_get_campaign_state():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    create_response = client.create_campaign(
+        name="CampaignStateTest",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    campaign_id = create_response["id"]
+
+    # Initially, the campaign state should be 'Initialized'
+    state_response = client.get_campaign_state(id=campaign_id)
+    assert state_response["state"] == "Initialized"
+
+    # Start the campaign
+    client.start_campaign(id=campaign_id)
+    state_response = client.get_campaign_state(id=campaign_id)
+    assert state_response["state"] == "Running"
+
+    # Stop the campaign
+    client.stop_campaign(id=campaign_id)
+    state_response = client.get_campaign_state(id=campaign_id)
+    assert state_response["state"] == "Stopped"
+
+
+@mock_aws
+def test_get_campaign_state_invalid_id():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    with pytest.raises(client.exceptions.ResourceNotFoundException):
+        client.get_campaign_state(id="non-existent-id")
+
+
+@mock_aws
+def test_pause_campaign():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    create_response = client.create_campaign(
+        name="CampaignToPause",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    campaign_id = create_response["id"]
+
+    # Start the campaign
+    client.start_campaign(id=campaign_id)
+
+    # Pause the campaign
+    client.pause_campaign(id=campaign_id)
+    state_response = client.get_campaign_state(id=campaign_id)
+
+    assert state_response["state"] == "Paused"
+
+
+@mock_aws
+def test_resume_campaign():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    create_response = client.create_campaign(
+        name="CampaignToResume",
+        connectInstanceId="12345678-1234-1234-1234-123456789012",
+        dialerConfig={
+            "progressiveDialerConfig": {
+                "bandwidthAllocation": 1.0,
+                "dialingCapacity": 2.0,
+            }
+        },
+        outboundCallConfig={
+            "connectContactFlowId": "12345678-1234-1234-1234-123456789012",
+        },
+    )
+
+    campaign_id = create_response["id"]
+
+    # Start the campaign
+    client.start_campaign(id=campaign_id)
+
+    # Pause the campaign
+    client.pause_campaign(id=campaign_id)
+
+    # Resume the campaign
+    client.resume_campaign(id=campaign_id)
+    state_response = client.get_campaign_state(id=campaign_id)
+
+    assert state_response["state"] == "Running"
+
+
+@mock_aws
+def test_pause_campaign_invalid_id():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    with pytest.raises(client.exceptions.ResourceNotFoundException):
+        client.pause_campaign(id="non-existent-id")
+
+
+@mock_aws
+def test_resume_campaign_invalid_id():
+    client = boto3.client("connectcampaigns", region_name="us-east-1")
+
+    with pytest.raises(client.exceptions.ResourceNotFoundException):
+        client.resume_campaign(id="non-existent-id")
diff --git a/tests/test_ram/test_ram.py b/tests/test_ram/test_ram.py
index da782ad8d..5a3b96c9d 100644
--- a/tests/test_ram/test_ram.py
+++ b/tests/test_ram/test_ram.py
@@ -8,6 +8,7 @@ from botocore.exceptions import ClientError
 
 from moto import mock_aws
 from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
+from moto.ram.models import ResourceAccessManagerBackend
 
 
 @mock_aws
@@ -530,3 +531,52 @@ def test_get_resource_share_associations_errors():
         "You cannot specify a principal when the association type is RESOURCE"
         in ex.response["Error"]["Message"]
     )
+
+
+@mock_aws
+@pytest.mark.parametrize(
+    "resource_region_scope, expect_error, error_message",
+    [
+        ({}, False, None),  # default value is "ALL"
+        ({"resourceRegionScope": "ALL"}, False, None),
+        ({"resourceRegionScope": "GLOBAL"}, False, None),
+        ({"resourceRegionScope": "REGIONAL"}, False, None),
+        (
+            {"resourceRegionScope": "INVALID"},
+            True,
+            "INVALID is not a valid resource region scope value. Specify a valid value and try again.",
+        ),
+    ],
+    ids=[
+        "default_region_scope",
+        "all_region_scope",
+        "global_region_scope",
+        "regional_region_scope",
+        "invalid_region_scope",
+    ],
+)
+def test_list_resource_types(resource_region_scope, expect_error, error_message):
+    client = boto3.client("ram", region_name="us-east-1")
+    region_scope = resource_region_scope.get("resourceRegionScope")
+
+    if expect_error:
+        with pytest.raises(ClientError) as e:
+            client.list_resource_types(**resource_region_scope)
+        ex = e.value
+        assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400
+        assert "InvalidParameterException" in ex.response["Error"]["Code"]
+        assert ex.response["Error"]["Message"] == error_message
+    else:
+        response = client.list_resource_types(**resource_region_scope)
+        expected_types = ResourceAccessManagerBackend.RESOURCE_TYPES
+        if region_scope == "GLOBAL":
+            expected_types = [
+                rt for rt in expected_types if rt["resourceRegionScope"] == "GLOBAL"
+            ]
+        elif region_scope == "REGIONAL":
+            expected_types = [
+                rt for rt in expected_types if rt["resourceRegionScope"] == "REGIONAL"
+            ]
+
+        assert "resourceTypes" in response
+        assert response["resourceTypes"] == expected_types
diff --git a/tests/test_rds/test_rds_clusters.py b/tests/test_rds/test_rds_clusters.py
index ec857c7c1..7fc5b9a57 100644
--- a/tests/test_rds/test_rds_clusters.py
+++ b/tests/test_rds/test_rds_clusters.py
@@ -2006,3 +2006,17 @@ def test_db_cluster_identifier_is_case_insensitive(client):
 
     response = client.delete_db_cluster(DBClusterIdentifier="xXyY")
     assert response["DBCluster"]["DBClusterIdentifier"] == "xxyy"
+
+
+@mock_aws
+def test_db_cluster_attributes(client):
+    cluster = client.create_db_cluster(
+        DBClusterIdentifier="test-cluster",
+        Engine="aurora-postgresql",
+        MasterUsername="root",
+        MasterUserPassword="password",
+        PubliclyAccessible=True,
+    )["DBCluster"]
+    assert cluster["DBClusterIdentifier"] == "test-cluster"
+    assert cluster["Engine"] == "aurora-postgresql"
+    assert cluster["PubliclyAccessible"] is True