Repository: getmoto/moto

Issue:
SSM tag methods should error with InvalidResourceId if not there
In #1024 changes were made to emulate tagging methods in the ssm subsystem.  Unfortunately they do not behave like AWS does.  When a resource is not available, AWS returns an `InvalidResourceId` error.  Here is an example I recorded with vcrpy:

```
  request:
    method: POST
    uri: https://ssm.us-west-2.amazonaws.com/
    body: '{"ResourceType": "Parameter", "ResourceId": "special-not-there-4"}'
  response:
    body:
      string: '{"__type":"InvalidResourceId"}'
    status:
      code: 400
      message: Bad Request
```

Here is the response from moto, which is incorrect:

```
(Pdb) pp client.list_tags_for_resource(ResourceType="Parameter", ResourceId="special-not-there-4")
{'ResponseMetadata': {'HTTPHeaders': {'server': 'amazon.com'},
                      'HTTPStatusCode': 200,
                      'RetryAttempts': 0},
 'TagList': []}
```


Discussion:

Resolving patch:
diff --git a/moto/ssm/exceptions.py b/moto/ssm/exceptions.py
--- a/moto/ssm/exceptions.py
+++ b/moto/ssm/exceptions.py
@@ -23,6 +23,24 @@ def __init__(self, message):
         super(InvalidFilterValue, self).__init__("InvalidFilterValue", message)
 
 
+class InvalidResourceId(JsonRESTError):
+    code = 400
+
+    def __init__(self):
+        super(InvalidResourceId, self).__init__(
+            "InvalidResourceId", "Invalid Resource Id"
+        )
+
+
+class InvalidResourceType(JsonRESTError):
+    code = 400
+
+    def __init__(self):
+        super(InvalidResourceType, self).__init__(
+            "InvalidResourceType", "Invalid Resource Type"
+        )
+
+
 class ParameterNotFound(JsonRESTError):
     code = 400
 
diff --git a/moto/ssm/models.py b/moto/ssm/models.py
--- a/moto/ssm/models.py
+++ b/moto/ssm/models.py
@@ -38,6 +38,8 @@
     ParameterMaxVersionLimitExceeded,
     DocumentPermissionLimit,
     InvalidPermissionType,
+    InvalidResourceId,
+    InvalidResourceType,
 )
 
 
@@ -835,6 +837,7 @@ def delete_document(self, name, document_version, version_name, force):
             documents.delete(*keys_to_delete)
 
             if len(documents.versions) == 0:
+                self._resource_tags.get("Document", {}).pop(name, None)
                 del self._documents[name]
 
     def get_document(self, name, document_version, version_name, document_format):
@@ -1029,6 +1032,7 @@ def modify_document_permission(
         )
 
     def delete_parameter(self, name):
+        self._resource_tags.get("Parameter", {}).pop(name, None)
         return self._parameters.pop(name, None)
 
     def delete_parameters(self, names):
@@ -1037,6 +1041,7 @@ def delete_parameters(self, names):
             try:
                 del self._parameters[name]
                 result.append(name)
+                self._resource_tags.get("Parameter", {}).pop(name, None)
             except KeyError:
                 pass
         return result
@@ -1611,18 +1616,44 @@ def put_parameter(
         return version
 
     def add_tags_to_resource(self, resource_type, resource_id, tags):
+        self._validate_resource_type_and_id(resource_type, resource_id)
         for key, value in tags.items():
             self._resource_tags[resource_type][resource_id][key] = value
 
     def remove_tags_from_resource(self, resource_type, resource_id, keys):
+        self._validate_resource_type_and_id(resource_type, resource_id)
         tags = self._resource_tags[resource_type][resource_id]
         for key in keys:
             if key in tags:
                 del tags[key]
 
     def list_tags_for_resource(self, resource_type, resource_id):
+        self._validate_resource_type_and_id(resource_type, resource_id)
         return self._resource_tags[resource_type][resource_id]
 
+    def _validate_resource_type_and_id(self, resource_type, resource_id):
+        if resource_type == "Parameter":
+            if resource_id not in self._parameters:
+                raise InvalidResourceId()
+            else:
+                return
+        elif resource_type == "Document":
+            if resource_id not in self._documents:
+                raise InvalidResourceId()
+            else:
+                return
+        elif resource_type not in (
+            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.remove_tags_from_resource
+            "ManagedInstance",
+            "MaintenanceWindow",
+            "PatchBaseline",
+            "OpsItem",
+            "OpsMetadata",
+        ):
+            raise InvalidResourceType()
+        else:
+            raise InvalidResourceId()
+
     def send_command(self, **kwargs):
         command = Command(
             comment=kwargs.get("Comment", ""),


Test patch:
diff --git a/tests/test_ssm/test_ssm_boto3.py b/tests/test_ssm/test_ssm_boto3.py
--- a/tests/test_ssm/test_ssm_boto3.py
+++ b/tests/test_ssm/test_ssm_boto3.py
@@ -1040,7 +1040,7 @@ def test_describe_parameters_tags():
 
 
 @mock_ssm
-def test_tags_in_list_tags_from_resource():
+def test_tags_in_list_tags_from_resource_parameter():
     client = boto3.client("ssm", region_name="us-east-1")
 
     client.put_parameter(
@@ -1053,9 +1053,32 @@ def test_tags_in_list_tags_from_resource():
     tags = client.list_tags_for_resource(
         ResourceId="/spam/eggs", ResourceType="Parameter"
     )
-
     assert tags.get("TagList") == [{"Key": "spam", "Value": "eggs"}]
 
+    client.delete_parameter(Name="/spam/eggs")
+
+    with pytest.raises(ClientError) as ex:
+        client.list_tags_for_resource(ResourceType="Parameter", ResourceId="/spam/eggs")
+    assert ex.value.response["Error"]["Code"] == "InvalidResourceId"
+
+
+@mock_ssm
+def test_tags_invalid_resource_id():
+    client = boto3.client("ssm", region_name="us-east-1")
+
+    with pytest.raises(ClientError) as ex:
+        client.list_tags_for_resource(ResourceType="Parameter", ResourceId="bar")
+    assert ex.value.response["Error"]["Code"] == "InvalidResourceId"
+
+
+@mock_ssm
+def test_tags_invalid_resource_type():
+    client = boto3.client("ssm", region_name="us-east-1")
+
+    with pytest.raises(ClientError) as ex:
+        client.list_tags_for_resource(ResourceType="foo", ResourceId="bar")
+    assert ex.value.response["Error"]["Code"] == "InvalidResourceType"
+
 
 @mock_ssm
 def test_get_parameter_invalid():
@@ -1619,12 +1642,21 @@ def test_get_parameter_history_missing_parameter():
 def test_add_remove_list_tags_for_resource():
     client = boto3.client("ssm", region_name="us-east-1")
 
+    with pytest.raises(ClientError) as ce:
+        client.add_tags_to_resource(
+            ResourceId="test",
+            ResourceType="Parameter",
+            Tags=[{"Key": "test-key", "Value": "test-value"}],
+        )
+    assert ce.value.response["Error"]["Code"] == "InvalidResourceId"
+
+    client.put_parameter(Name="test", Value="value", Type="String")
+
     client.add_tags_to_resource(
         ResourceId="test",
         ResourceType="Parameter",
         Tags=[{"Key": "test-key", "Value": "test-value"}],
     )
-
     response = client.list_tags_for_resource(
         ResourceId="test", ResourceType="Parameter"
     )
diff --git a/tests/test_ssm/test_ssm_docs.py b/tests/test_ssm/test_ssm_docs.py
--- a/tests/test_ssm/test_ssm_docs.py
+++ b/tests/test_ssm/test_ssm_docs.py
@@ -10,6 +10,9 @@
 import hashlib
 import copy
 import pkgutil
+import pytest
+
+from botocore.exceptions import ClientError
 
 from moto.core import ACCOUNT_ID
 
@@ -767,3 +770,32 @@ def test_list_documents():
         Filters=[{"Key": "TargetType", "Values": ["/AWS::EC2::Instance"]}]
     )
     len(response["DocumentIdentifiers"]).should.equal(1)
+
+
+@mock_ssm
+def test_tags_in_list_tags_from_resource_document():
+    template_file = _get_yaml_template()
+    json_doc = yaml.safe_load(template_file)
+
+    client = boto3.client("ssm", region_name="us-east-1")
+
+    client.create_document(
+        Content=json.dumps(json_doc),
+        Name="TestDocument",
+        DocumentType="Command",
+        DocumentFormat="JSON",
+        Tags=[{"Key": "spam", "Value": "ham"}],
+    )
+
+    tags = client.list_tags_for_resource(
+        ResourceId="TestDocument", ResourceType="Document"
+    )
+    assert tags.get("TagList") == [{"Key": "spam", "Value": "ham"}]
+
+    client.delete_document(Name="TestDocument")
+
+    with pytest.raises(ClientError) as ex:
+        client.list_tags_for_resource(
+            ResourceType="Document", ResourceId="TestDocument"
+        )
+    assert ex.value.response["Error"]["Code"] == "InvalidResourceId"