Repository: getmoto/moto
Pull request #8835 (2025-04-20 17:57:24)

feat(acm-pca): add support for `PutPolicy`, `GetPolicy`, and `DeletePolicy`
## Description

Adds support for the following in ACM:

- https://docs.aws.amazon.com/privateca/latest/APIReference/API_PutPolicy.html
- https://docs.aws.amazon.com/privateca/latest/APIReference/API_GetPolicy.html
- https://docs.aws.amazon.com/privateca/latest/APIReference/API_DeletePolicy.html

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8835?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 `89.74359%` with `4 lines` in your changes missing coverage. Please review.
> Project coverage is 92.82%. Comparing base [(`46c088c`)](https://app.codecov.io/gh/getmoto/moto/commit/46c088c770b5230a6df4fd1ba3362bf6ca2144a5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`cbcb56a`)](https://app.codecov.io/gh/getmoto/moto/commit/cbcb56aab7b4de6ddab1c49e4ef6ef802831bd38?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 1 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/getmoto/moto/pull/8835?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/acmpca/exceptions.py](https://app.codecov.io/gh/getmoto/moto/pull/8835?src=pr&el=tree&filepath=moto%2Facmpca%2Fexceptions.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9hY21wY2EvZXhjZXB0aW9ucy5weQ==) | 66.66% | [3 Missing :warning: ](https://app.codecov.io/gh/getmoto/moto/pull/8835?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) |
| [moto/acmpca/models.py](https://app.codecov.io/gh/getmoto/moto/pull/8835?src=pr&el=tree&filepath=moto%2Facmpca%2Fmodels.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9hY21wY2EvbW9kZWxzLnB5) | 92.85% | [1 Missing :warning: ](https://app.codecov.io/gh/getmoto/moto/pull/8835?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) |

:x: Your patch check has failed because the patch coverage (89.74%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the [target](https://docs.codecov.com/docs/commit-status#target) coverage.

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


```diff
@@            Coverage Diff             @@
##           master    #8835      +/-   ##
==========================================
- Coverage   92.82%   92.82%   -0.01%     
==========================================
  Files        1266     1266              
  Lines      110584   110623      +39     
==========================================
+ Hits       102651   102686      +35     
- Misses       7933     7937       +4     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/8835/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/8835/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `28.15% <23.07%> (-0.01%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/8835/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `92.79% <89.74%> (-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/8835?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/docs/docs/services/acm-pca.rst b/docs/docs/services/acm-pca.rst
index 9a7d25dac..a11ad60ff 100644
--- a/docs/docs/services/acm-pca.rst
+++ b/docs/docs/services/acm-pca.rst
@@ -25,7 +25,7 @@ acm-pca
 - [ ] create_permission
 - [X] delete_certificate_authority
 - [ ] delete_permission
-- [ ] delete_policy
+- [X] delete_policy
 - [X] describe_certificate_authority
 - [ ] describe_certificate_authority_audit_report
 - [X] get_certificate
@@ -35,7 +35,7 @@ acm-pca
 
 - [X] get_certificate_authority_certificate
 - [X] get_certificate_authority_csr
-- [ ] get_policy
+- [X] get_policy
 - [X] import_certificate_authority_certificate
 - [X] issue_certificate
   
@@ -50,7 +50,7 @@ acm-pca
         Pagination is not yet implemented
         
 
-- [ ] put_policy
+- [X] put_policy
 - [ ] restore_certificate_authority
 - [X] revoke_certificate
   
diff --git a/docs/docs/services/acm.rst b/docs/docs/services/acm.rst
index cebe0d025..9a1c6cf95 100644
--- a/docs/docs/services/acm.rst
+++ b/docs/docs/services/acm.rst
@@ -18,12 +18,12 @@ acm
 - [X] delete_certificate
 - [X] describe_certificate
 - [X] export_certificate
-- [ ] get_account_configuration
+- [X] get_account_configuration
 - [X] get_certificate
 - [X] import_certificate
 - [X] list_certificates
 - [ ] list_tags_for_certificate
-- [ ] put_account_configuration
+- [X] put_account_configuration
 - [X] remove_tags_from_certificate
 - [ ] renew_certificate
 - [X] request_certificate
diff --git a/moto/acm/models.py b/moto/acm/models.py
index 913b9da9c..f5af53ca0 100644
--- a/moto/acm/models.py
+++ b/moto/acm/models.py
@@ -435,6 +435,14 @@ class CertBundle(BaseModel):
         return "<Certificate>"
 
 
+class AccountConfiguration:
+    def __init__(self, days_before_expiry: int = 45):
+        self.days_before_expiry = days_before_expiry
+
+    def to_dict(self):  # type: ignore
+        return {"ExpiryEvents": {"DaysBeforeExpiry": self.days_before_expiry}}
+
+
 class AWSCertificateManagerBackend(BaseBackend):
     MIN_PASSPHRASE_LEN = 4
 
@@ -442,6 +450,7 @@ class AWSCertificateManagerBackend(BaseBackend):
         super().__init__(region_name, account_id)
         self._certificates: Dict[str, CertBundle] = {}
         self._idempotency_tokens: Dict[str, Any] = {}
+        self._account_config = AccountConfiguration()
 
     def set_certificate_in_use_by(self, arn: str, load_balancer_name: str) -> None:
         if arn not in self._certificates:
@@ -604,5 +613,23 @@ class AWSCertificateManagerBackend(BaseBackend):
 
         return certificate, certificate_chain, private_key
 
+    def get_account_configuration(self) -> Dict[str, Any]:
+        return self._account_config.to_dict()  # type: ignore
+
+    def put_account_configuration(
+        self, days_before_expiry: int, idempotency_token: str
+    ) -> None:
+        if idempotency_token is not None:
+            arn = self._get_arn_from_idempotency_token(idempotency_token)
+            if arn:
+                return
+
+        if days_before_expiry < 1 or days_before_expiry > 90:
+            raise AWSValidationException("DaysBeforeExpiry must be between 1 and 90")
+
+        self._account_config = AccountConfiguration(days_before_expiry)
+        if idempotency_token is not None:
+            self._set_idempotency_token_arn(idempotency_token, "account_config")
+
 
 acm_backends = BackendDict(AWSCertificateManagerBackend, "acm")
diff --git a/moto/acm/responses.py b/moto/acm/responses.py
index 97c27cae1..8c1320c31 100644
--- a/moto/acm/responses.py
+++ b/moto/acm/responses.py
@@ -251,3 +251,31 @@ class AWSCertificateManagerResponse(BaseResponse):
                 PrivateKey=private_key,
             )
         )
+
+    def get_account_configuration(self) -> str:
+        config = self.acm_backend.get_account_configuration()
+        return json.dumps(config)
+
+    def put_account_configuration(self) -> str:
+        expiry_events = self._get_param("ExpiryEvents")
+        idempotency_token = self._get_param("IdempotencyToken")
+
+        if expiry_events is None:
+            msg = "A required parameter for the specified action is not supplied."
+            return (  # type: ignore
+                json.dumps({"__type": "MissingParameter", "message": msg}),
+                dict(status=400),
+            )
+
+        days_before_expiry = expiry_events.get("DaysBeforeExpiry")
+        if days_before_expiry is None:
+            msg = "DaysBeforeExpiry is required in ExpiryEvents."
+            return (  # type: ignore
+                json.dumps({"__type": "ValidationException", "message": msg}),
+                dict(status=400),
+            )
+
+        self.acm_backend.put_account_configuration(
+            days_before_expiry, idempotency_token
+        )
+        return "{}"
diff --git a/moto/acmpca/exceptions.py b/moto/acmpca/exceptions.py
index 674d2d89e..93794df6b 100644
--- a/moto/acmpca/exceptions.py
+++ b/moto/acmpca/exceptions.py
@@ -36,3 +36,27 @@ class MalformedCertificateAuthorityException(JsonRESTError):
             "MalformedCertificateAuthorityException",
             "Malformed certificate.",
         )
+
+
+class InvalidPolicyException(JsonRESTError):
+    def __init__(self) -> None:
+        super().__init__(
+            "InvalidPolicyException",
+            "The resource policy is invalid or is missing a required statement.",
+        )
+
+
+class LockoutPreventedException(JsonRESTError):
+    def __init__(self) -> None:
+        super().__init__(
+            "LockoutPreventedException",
+            "The current action was prevented because it would lock the caller out from performing subsequent actions.",
+        )
+
+
+class ConcurrentModificationException(JsonRESTError):
+    def __init__(self) -> None:
+        super().__init__(
+            "ConcurrentModificationException",
+            "A previous update to your private CA is still ongoing.",
+        )
diff --git a/moto/acmpca/models.py b/moto/acmpca/models.py
index bcff2ffa6..533449de2 100644
--- a/moto/acmpca/models.py
+++ b/moto/acmpca/models.py
@@ -49,6 +49,7 @@ class CertificateAuthority(BaseModel):
         self.status = "PENDING_CERTIFICATE"
         self.usage_mode = "SHORT_LIVED_CERTIFICATE"
         self.security_standard = security_standard or "FIPS_140_2_LEVEL_3_OR_HIGHER"
+        self.policy: Optional[str] = None
 
         self.password = str(mock_random.uuid4()).encode("utf-8")
 
@@ -458,5 +459,30 @@ class ACMPCABackend(BaseBackend):
     ) -> None:
         self.tagger.untag_resource_using_tags(certificate_authority_arn, tags)
 
+    def put_policy(self, resource_arn: str, policy: str) -> None:
+        """
+        Attaches a resource-based policy to a private CA.
+        """
+        ca = self.describe_certificate_authority(resource_arn)
+        if ca.status != "ACTIVE":
+            raise InvalidStateException(resource_arn)
+        ca.policy = policy
+
+    def get_policy(self, resource_arn: str) -> str:
+        """
+        Retrieves the resource-based policy attached to a private CA.
+        """
+        ca = self.describe_certificate_authority(resource_arn)
+        if ca.policy is None:
+            raise ResourceNotFoundException(resource_arn)
+        return ca.policy
+
+    def delete_policy(self, resource_arn: str) -> None:
+        """
+        Deletes the resource-based policy attached to a private CA.
+        """
+        ca = self.describe_certificate_authority(resource_arn)
+        ca.policy = None
+
 
 acmpca_backends = BackendDict(ACMPCABackend, "acm-pca")
diff --git a/moto/acmpca/responses.py b/moto/acmpca/responses.py
index a023e60ce..2e66c1298 100644
--- a/moto/acmpca/responses.py
+++ b/moto/acmpca/responses.py
@@ -175,3 +175,22 @@ class ACMPCAResponse(BaseResponse):
             tags=tags,
         )
         return "{}"
+
+    def put_policy(self) -> str:
+        params = json.loads(self.body)
+        resource_arn = params.get("ResourceArn")
+        policy = params.get("Policy")
+        self.acmpca_backend.put_policy(resource_arn=resource_arn, policy=policy)
+        return "{}"
+
+    def get_policy(self) -> str:
+        params = json.loads(self.body)
+        resource_arn = params.get("ResourceArn")
+        policy = self.acmpca_backend.get_policy(resource_arn=resource_arn)
+        return json.dumps({"Policy": policy})
+
+    def delete_policy(self) -> str:
+        params = json.loads(self.body)
+        resource_arn = params.get("ResourceArn")
+        self.acmpca_backend.delete_policy(resource_arn=resource_arn)
+        return "{}"
diff --git a/moto/iam/models.py b/moto/iam/models.py
index 868e30797..a2a3da00b 100644
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -802,7 +802,7 @@ class Role(CloudFormationModel):
         account_id: str,
         region_name: str,
     ) -> None:
-        backend = iam_backends[account_id][get_partition(region_name)]
+        backend: IAMBackend = iam_backends[account_id][get_partition(region_name)]
         for profile in backend.instance_profiles.values():
             profile.delete_role(role_name=resource_name)
 
@@ -810,6 +810,13 @@ class Role(CloudFormationModel):
             if role.name == resource_name:
                 for arn in list(role.policies.keys()):
                     role.delete_policy(arn)
+
+        # We may have inline policies attached to this role - make sure that they don't reference a deleted role
+        # If the reference still exists, it's impossible to delete the policy, because on deletion
+        # it will try to detach itself from a role that no longer exists
+        for policy in backend.inline_policies.values():
+            policy.role_names = [n for n in policy.role_names if n != resource_name]
+
         backend.delete_role(resource_name)
 
     @property
diff --git a/moto/s3/exceptions.py b/moto/s3/exceptions.py
index 21a98e8d5..bfd600f7f 100644
--- a/moto/s3/exceptions.py
+++ b/moto/s3/exceptions.py
@@ -620,26 +620,6 @@ class HeadOnDeleteMarker(Exception):
         self.marker = marker
 
 
-class DaysMustNotProvidedForSelectRequest(S3ClientError):
-    code = 400
-
-    def __init__(self) -> None:
-        super().__init__(
-            "DaysMustNotProvidedForSelectRequest",
-            "`Days` must not be provided for select requests",
-        )
-
-
-class DaysMustProvidedExceptForSelectRequest(S3ClientError):
-    code = 400
-
-    def __init__(self) -> None:
-        super().__init__(
-            "DaysMustProvidedExceptForSelectRequest",
-            "`Days` must be provided except for select requests",
-        )
-
-
 class MethodNotAllowed(S3ClientError):
     code = 405
 
diff --git a/moto/s3/models.py b/moto/s3/models.py
index c34e30c57..6350801e7 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -40,8 +40,6 @@ from moto.s3.exceptions import (
     BucketNeedsToBeNew,
     CopyObjectMustChangeSomething,
     CrossLocationLoggingProhibitted,
-    DaysMustNotProvidedForSelectRequest,
-    DaysMustProvidedExceptForSelectRequest,
     EntityTooSmall,
     HeadOnDeleteMarker,
     InvalidBucketName,
@@ -3104,12 +3102,6 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
         if not key:
             raise MissingKey
 
-        if days is None and type_ is None:
-            raise DaysMustProvidedExceptForSelectRequest()
-
-        if days and type_:
-            raise DaysMustNotProvidedForSelectRequest()
-
         if key.storage_class not in ARCHIVE_STORAGE_CLASSES:
             raise InvalidObjectState(storage_class=key.storage_class)
         had_expiry_date = key.expiry_date is not None


Test patch:
diff --git a/tests/test_acm/test_acm.py b/tests/test_acm/test_acm.py
index 8052d56ca..004cdea2c 100644
--- a/tests/test_acm/test_acm.py
+++ b/tests/test_acm/test_acm.py
@@ -884,3 +884,30 @@ def test_certificate_expiration_status():
     with freeze_time(expiry_time + datetime.timedelta(days=1)):  # 1 day after
         resp = client.describe_certificate(CertificateArn=arn)
         assert resp["Certificate"]["Status"] == "EXPIRED"
+
+
+@mock_aws
+def test_account_configuration():
+    client = boto3.client("acm", region_name="eu-central-1")
+
+    # Test default configuration
+    response = client.get_account_configuration()
+    assert response["ExpiryEvents"]["DaysBeforeExpiry"] == 45
+
+    # Test successful update
+    response = client.put_account_configuration(
+        ExpiryEvents={"DaysBeforeExpiry": 30}, IdempotencyToken="test-token"
+    )
+
+    # Verify the update was successful
+    response = client.get_account_configuration()
+    assert response["ExpiryEvents"]["DaysBeforeExpiry"] == 30
+
+    # Test idempotency token - trying to update with same token but different value
+    response = client.put_account_configuration(
+        ExpiryEvents={"DaysBeforeExpiry": 60}, IdempotencyToken="test-token"
+    )
+
+    # Should still be 30 due to idempotency token
+    response = client.get_account_configuration()
+    assert response["ExpiryEvents"]["DaysBeforeExpiry"] == 30
diff --git a/tests/test_acmpca/test_acmpca.py b/tests/test_acmpca/test_acmpca.py
index 7fc19104c..edf131d81 100644
--- a/tests/test_acmpca/test_acmpca.py
+++ b/tests/test_acmpca/test_acmpca.py
@@ -1,4 +1,5 @@
 import datetime
+import json
 import pickle
 
 import boto3
@@ -636,3 +637,63 @@ def create_cert():
     )
 
     return cert.public_bytes(serialization.Encoding.PEM)
+
+
+@mock_aws
+def test_policy_operations():
+    client = boto3.client("acm-pca", region_name="us-east-1")
+
+    # Create and activate a CA
+    ca_arn = client.create_certificate_authority(
+        CertificateAuthorityConfiguration={
+            "KeyAlgorithm": "RSA_4096",
+            "SigningAlgorithm": "SHA512WITHRSA",
+            "Subject": {"CommonName": "test.example.com"},
+        },
+        CertificateAuthorityType="ROOT",
+    )["CertificateAuthorityArn"]
+
+    # Get CSR and issue certificate to activate the CA
+    csr = client.get_certificate_authority_csr(CertificateAuthorityArn=ca_arn)["Csr"]
+    certificate_arn = client.issue_certificate(
+        CertificateAuthorityArn=ca_arn,
+        Csr=csr,
+        SigningAlgorithm="SHA256WITHRSA",
+        TemplateArn="arn:aws:acm-pca:::template/RootCACertificate/V1",
+        Validity={"Type": "YEARS", "Value": 10},
+    )["CertificateArn"]
+    cert = client.get_certificate(
+        CertificateAuthorityArn=ca_arn, CertificateArn=certificate_arn
+    )["Certificate"]
+    client.import_certificate_authority_certificate(
+        CertificateAuthorityArn=ca_arn,
+        Certificate=cert,
+    )
+
+    # Test policy operations
+    policy = {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Effect": "Allow",
+                "Principal": {"AWS": "012345678901"},
+                "Action": ["acm-pca:IssueCertificate"],
+                "Resource": ca_arn,
+            }
+        ],
+    }
+
+    # Put policy
+    client.put_policy(ResourceArn=ca_arn, Policy=json.dumps(policy))
+
+    # Get and verify policy
+    response = client.get_policy(ResourceArn=ca_arn)
+    assert json.loads(response["Policy"]) == policy
+
+    # Delete policy
+    client.delete_policy(ResourceArn=ca_arn)
+
+    # Get policy should now fail
+    with pytest.raises(ClientError) as exc:
+        client.get_policy(ResourceArn=ca_arn)
+    assert exc.value.response["Error"]["Code"] == "ResourceNotFoundException"
diff --git a/tests/test_iam/test_iam_cloudformation.py b/tests/test_iam/test_iam_cloudformation.py
index 5650d81ca..d69c3388f 100644
--- a/tests/test_iam/test_iam_cloudformation.py
+++ b/tests/test_iam/test_iam_cloudformation.py
@@ -71,6 +71,50 @@ Resources:
         - !Ref RootRole
 """
 
+TEMPLATE_ROLE_AND_PROFILE = """
+---
+AWSTemplateFormatVersion: '2010-09-09'
+
+Resources:
+  Role:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+          - Effect: Allow
+            Principal:
+              Service: ecs-tasks.amazonaws.com
+            Action: sts:AssumeRole
+      Policies:
+        - PolicyName: StandardTaskPolicy
+          PolicyDocument:
+            Version: "2012-10-17"
+            Statement:
+              - Sid: AllowCloudwatchLogging
+                Effect: Allow
+                Action:
+                  - logs:CreateLogStream
+                  - logs:DescribeLogGroups
+                  - logs:DescribeLogStreams
+                Resource: '*'
+
+  Pol10:
+    Type: AWS::IAM::Policy
+    Properties:
+      PolicyName: AdditionalInlinePolicy
+      PolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+          - Sid: AllowCloudwatchLogging
+            Effect: Allow
+            Action:
+              - logs:PutLogEvents
+            Resource: '*'
+      Roles:
+        - !Ref Role
+"""
+
 
 # AWS::IAM::User Tests
 @mock_aws
@@ -1091,6 +1135,28 @@ Resources:
     assert exc.value.response["Error"]["Code"] == "NoSuchEntity"
 
 
+@mock_aws
+def test_delete_cfn_stack_with_policy_attached_to_role():
+    # The Stack has two resources, a Role and an InlinePolicy attached to this role.
+    # On deletion, there are two scenarios:
+    #
+    #    1. Policy is deleted first
+    #       a) removes policy from the roles it's attached to
+    #       b) removes the policy itself
+    #       c) removes the role last
+    #    2. Role is deleted first
+    #       a) removes the role
+    #       b) removes inline policy from the roles it's attached to
+    #       c) removes policy itself
+    #
+    # This test verifies that, at step 2b, we don't try to remove a policy from a role that no longer exist
+    cfn = boto3.client("cloudformation", region_name="us-east-1")
+
+    cfn.create_stack(StackName="stack1", TemplateBody=TEMPLATE_ROLE_AND_PROFILE)
+
+    cfn.delete_stack(StackName="stack1")
+
+
 # AWS::IAM::User AccessKeys
 @mock_aws
 def test_iam_cloudformation_create_user_with_access_key():
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
index 952496327..4815aa317 100644
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -711,32 +711,6 @@ def test_cannot_restore_standard_class_object():
     )
 
 
-@mock_aws
-def test_restore_object_invalid_request_params():
-    if not settings.TEST_DECORATOR_MODE:
-        raise SkipTest("Can't set transition directly in ServerMode")
-
-    s3_resource = boto3.resource("s3", region_name=DEFAULT_REGION_NAME)
-    bucket = s3_resource.Bucket("foobar")
-    bucket.create()
-
-    key = bucket.put_object(Key="the-key", Body=b"somedata", StorageClass="GLACIER")
-
-    # `Days` must be provided except for select requests
-    with pytest.raises(ClientError) as exc:
-        key.restore_object(RestoreRequest={})
-    err = exc.value.response["Error"]
-    assert err["Code"] == "DaysMustProvidedExceptForSelectRequest"
-    assert err["Message"] == "`Days` must be provided except for select requests"
-
-    # `Days` must not be provided for select requests
-    with pytest.raises(ClientError) as exc:
-        key.restore_object(RestoreRequest={"Days": 1, "Type": "SELECT"})
-    err = exc.value.response["Error"]
-    assert err["Code"] == "DaysMustNotProvidedForSelectRequest"
-    assert err["Message"] == "`Days` must not be provided for select requests"
-
-
 @mock_aws
 def test_get_versioning_status():
     s3_resource = boto3.resource("s3", region_name=DEFAULT_REGION_NAME)