Repository: localstack/localstack

Issue:
SSM - labeling more than 10 parameters does not work right
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

Adding and labeling more than 10 parameters only labels (or only returns) the first 10 labeled parameters.

### Expected Behavior

Adding and labeling more than 10 parameters should add & then return all the parameters.

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

docker-compose.yml

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

```
params = [ ... ]  # create a list of **more** than 10 parameters ( Name + Value)
for param of params:
       # add the parameter (returns 200)
        res = ssm.put_parameter(Name=param["Name"], Value=param["Value], Type='String')

       # label the returned version with 'latest' (returns 200)
        label_res = ssm.label_parameter_version(Name=path, ParameterVersion=res['Version'], Labels=['latest'])

       # get the added parameter (returns the parameter successfully)
        prm = ssm.get_parameter(Name=path)

# get all parameters with the 'latest' label - this returns the first 10 parameters (although `MaxResults` is 100), and also doesn't return a `NextToken` property to iterate through the next batch of parameters.
ps = ssm.get_parameters_by_path(Path=base_path,
                                        ParameterFilters=[  {  'Key': 'Label',  'Values': [  'latest'  ] } ],
                                        MaxResults=100) 

```


### Environment

```markdown
- OS: Windows 10
- LocalStack: 0.12.17.5
```


### Anything else?

_No response_


Discussion:

Resolving patch:
diff --git a/localstack/services/ssm/ssm_listener.py b/localstack/services/ssm/ssm_listener.py
--- a/localstack/services/ssm/ssm_listener.py
+++ b/localstack/services/ssm/ssm_listener.py
@@ -1,7 +1,6 @@
 import json
 import logging
 import time
-import traceback
 
 from requests.models import Request
 
@@ -90,57 +89,6 @@ def get_params_and_secrets(names):
     return result
 
 
-def get_params_by_path_with_labels(
-    path="", param_filters=None, labels_to_filter=None, recursive=False
-):
-    ssm_client = aws_stack.connect_to_service("ssm")
-    result = {"Parameters": []}
-    filters = [{"Key": "Path", "Values": [path]}]
-    filters.extend(param_filters)
-    if recursive:
-        filters[0]["Option"] = "Recursive"
-
-    def filter_by_label(param, labels):
-        for label in param["Labels"]:
-            if label in labels:
-                return param
-
-    try:
-        # Get all the params in the path
-        params_in_path = ssm_client.describe_parameters(ParameterFilters=filters)["Parameters"]
-
-        # Get parameter with all its labels (for all the parameters in params_in_path)
-        # Labels of the parameters can be obtained by calling get_parameter_history with parameter name
-        all_params = []
-        for params in params_in_path:
-            all_params.extend(ssm_client.get_parameter_history(Name=params["Name"])["Parameters"])
-
-        # Filter the params with matched labels
-        filtered_params = list(
-            filter(
-                lambda param: filter_by_label(param=param, labels=labels_to_filter),
-                all_params,
-            )
-        )
-
-        # Get details of the filtered params to return
-        # This step is needed because get_parameter_history doesn't return parameter's ARN
-        details_of_filtered_params = list(
-            map(
-                lambda param: ssm_client.get_parameter(Name=param["Name"])["Parameter"],
-                filtered_params,
-            )
-        )
-        result["Parameters"].extend(details_of_filtered_params)
-    except Exception as e:
-        LOG.info(
-            "Unable to get SSM parameters by path and filter by labels : %s %s"
-            % (e, traceback.format_exc())
-        )
-        raise e
-    return result
-
-
 def notify_event_subscribers(data, target_header):
     """Publish an EventBridge event to notify subscribers of changes."""
     if not is_api_enabled("events"):
@@ -192,22 +140,7 @@ def forward_request(self, method, path, data, headers):
                             secret = get_secrets_information(name, resource_name)
                             if secret is not None:
                                 return secret
-            elif target == ACTION_GET_PARAMS_BY_PATH and data.get("ParameterFilters"):
-                params_filters = data.get("ParameterFilters") or []
-                labels = []
-                for filter in params_filters:
-                    if filter["Key"] == "Label":
-                        labels = filter["Values"]
-                        params_filters.remove(filter)
-                if labels:
-                    path = data.get("Path")
-                    recursive = data.get("Recursive") or False
-                    return get_params_by_path_with_labels(
-                        path=path,
-                        param_filters=params_filters,
-                        labels_to_filter=labels,
-                        recursive=recursive,
-                    )
+
             # send event notifications
             if target in EVENT_BRIDGE_OPERATIONS:
                 notify_event_subscribers(data, target)


Test patch:
diff --git a/tests/integration/fixtures.py b/tests/integration/fixtures.py
--- a/tests/integration/fixtures.py
+++ b/tests/integration/fixtures.py
@@ -343,6 +343,35 @@ def _create_lambda_function(*args, **kwargs):
         lambda_client.delete_function(FunctionName=arn)
 
 
+@pytest.fixture
+def create_parameter(ssm_client):
+    params = []
+
+    def _create_parameter(**kwargs):
+        params.append(kwargs["Name"])
+        return ssm_client.put_parameter(**kwargs)
+
+    yield _create_parameter
+
+    for param in params:
+        ssm_client.delete_parameter(Name=param)
+
+
+@pytest.fixture
+def create_secret(secretsmanager_client):
+    items = []
+
+    def _create_parameter(**kwargs):
+        create_response = secretsmanager_client.create_secret(**kwargs)
+        items.append(create_response["ARN"])
+        return create_response
+
+    yield _create_parameter
+
+    for item in items:
+        secretsmanager_client.delete_secret(SecretId=item)
+
+
 only_in_alpine = pytest.mark.skipif(
     not is_alpine(),
     reason="test only applicable if run in alpine",
diff --git a/tests/integration/test_ssm.py b/tests/integration/test_ssm.py
--- a/tests/integration/test_ssm.py
+++ b/tests/integration/test_ssm.py
@@ -1,96 +1,97 @@
-import unittest
+import pytest
 
-from localstack.utils.aws import aws_stack
+from localstack.utils.common import short_uid
 
 
-class SSMTest(unittest.TestCase):
-    def test_describe_parameters(self):
-        ssm_client = aws_stack.connect_to_service("ssm")
+def _assert(search_name, param_name, ssm_client):
+    def do_assert(result):
+        assert len(result) > 0
+        assert param_name == result[0]["Name"]
+        assert "123" == result[0]["Value"]
 
-        response = ssm_client.describe_parameters()
-        self.assertIn("Parameters", response)
-        self.assertIsInstance(response["Parameters"], list)
+    response = ssm_client.get_parameter(Name=search_name)
+    do_assert([response["Parameter"]])
+
+    response = ssm_client.get_parameters(Names=[search_name])
+    do_assert(response["Parameters"])
 
-    def test_put_parameters(self):
-        ssm_client = aws_stack.connect_to_service("ssm")
 
-        ssm_client.put_parameter(
-            Name="test_put",
+# TODO: fix AWS compatibility
+class TestSSM:
+    def test_describe_parameters(self, ssm_client):
+        response = ssm_client.describe_parameters()
+        assert "Parameters" in response
+        assert isinstance(response["Parameters"], list)
+
+    def test_put_parameters(self, ssm_client, create_parameter):
+        param_name = f"param-{short_uid()}"
+        create_parameter(
+            Name=param_name,
             Description="test",
             Value="123",
             Type="String",
         )
 
-        self._assert("test_put", "test_put")
-        self._assert("/test_put", "test_put")
+        _assert(param_name, param_name, ssm_client)
+        _assert(f"/{param_name}", param_name, ssm_client)  # TODO: not valid
 
-    def test_hierarchical_parameter(self):
-        ssm_client = aws_stack.connect_to_service("ssm")
-
-        ssm_client.put_parameter(
-            Name="/a/b/c",
+    # TODO botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the GetParameter operation: Parameter name: can't be prefixed with "ssm" (case-insensitive). If formed as a path, it can consist of sub-paths divided by slash symbol; each sub-path can be formed as a mix of letters, numbers and the following 3 symbols .-_
+    def test_hierarchical_parameter(self, ssm_client, create_parameter):
+        param_a = f"{short_uid()}"
+        create_parameter(
+            Name=f"/{param_a}/b/c",
             Value="123",
             Type="String",
         )
 
-        self._assert("/a/b/c", "/a/b/c")
-        self._assert("/a//b//c", "/a/b/c")
-        self._assert("a/b//c", "/a/b/c")
-
-    def test_get_secret_parameter(self):
-        ssm_client = aws_stack.connect_to_service("ssm")
-        sec_client = aws_stack.connect_to_service("secretsmanager")
+        _assert(f"/{param_a}/b/c", f"/{param_a}/b/c", ssm_client)
+        _assert(f"/{param_a}//b//c", f"/{param_a}/b/c", ssm_client)
+        _assert(f"{param_a}/b//c", f"/{param_a}/b/c", ssm_client)
 
-        secret_name = "test_secret"
-        sec_client.create_secret(
+    # TODO botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the GetParameter operation: WithDecryption flag must be True for retrieving a Secret Manager secret.
+    def test_get_secret_parameter(self, ssm_client, secretsmanager_client, create_secret):
+        secret_name = f"test_secret-{short_uid()}"
+        create_secret(
             Name=secret_name,
             SecretString="my_secret",
             Description="testing creation of secrets",
         )
 
-        result = ssm_client.get_parameter(
-            Name="/aws/reference/secretsmanager/{0}".format(secret_name)
-        )
-
-        self.assertEqual(
-            "/aws/reference/secretsmanager/{0}".format(secret_name),
-            result.get("Parameter").get("Name"),
-        )
-        self.assertEqual("my_secret", result.get("Parameter").get("Value"))
+        result = ssm_client.get_parameter(Name=f"/aws/reference/secretsmanager/{secret_name}")
+        assert f"/aws/reference/secretsmanager/{secret_name}" == result.get("Parameter").get("Name")
+        assert "my_secret" == result.get("Parameter").get("Value")
 
         source_result = result.get("Parameter").get("SourceResult")
-        self.assertTrue(source_result is not None, "SourceResult should be present")
-        self.assertTrue(type(source_result) is str, "SourceResult should be a string")
-
-    def test_get_inexistent_secret(self):
-        ssm_client = aws_stack.connect_to_service("ssm")
-        self.assertRaises(
-            ssm_client.exceptions.ParameterNotFound,
-            ssm_client.get_parameter,
-            Name="/aws/reference/secretsmanager/inexistent",
-        )
-
-    def test_get_parameters_and_secrets(self):
-        ssm_client = aws_stack.connect_to_service("ssm")
-        sec_client = aws_stack.connect_to_service("secretsmanager")
+        assert source_result is not None, "SourceResult should be present"
+        assert type(source_result) is str, "SourceResult should be a string"
+
+    # TODO: botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the GetParameter operation: WithDecryption flag must be True for retrieving a Secret Manager secret.
+    def test_get_inexistent_secret(self, ssm_client):
+        with pytest.raises(ssm_client.exceptions.ParameterNotFound):
+            ssm_client.get_parameter(Name="/aws/reference/secretsmanager/inexistent")
+
+    # TODO: AssertionError: assert '/aws/reference/secretsmanager/9763a545_test_secret_params' in ['inexistent_param', '/aws/reference/secretsmanager/inexistent_secret']
+    def test_get_parameters_and_secrets(
+        self, ssm_client, secretsmanager_client, create_parameter, create_secret
+    ):
+        param_name = f"param-{short_uid()}"
         secret_path = "/aws/reference/secretsmanager/"
+        secret_name = f"{short_uid()}_test_secret_params"
+        complete_secret = secret_path + secret_name
 
-        param_name = "test_param"
-        ssm_client.put_parameter(
+        create_parameter(
             Name=param_name,
             Description="test",
             Value="123",
             Type="String",
         )
 
-        secret_name = "test_secret_params"
-        sec_client.create_secret(
+        create_secret(
             Name=secret_name,
             SecretString="my_secret",
             Description="testing creation of secrets",
         )
 
-        complete_secret = secret_path + secret_name
         response = ssm_client.get_parameters(
             Names=[
                 param_name,
@@ -103,33 +104,25 @@ def test_get_parameters_and_secrets(self):
         not_found = response.get("InvalidParameters")
 
         for param in found:
-            self.assertIn(param["Name"], [param_name, complete_secret])
+            assert param["Name"] in [param_name, complete_secret]
         for param in not_found:
-            self.assertIn(param, ["inexistent_param", secret_path + "inexistent_secret"])
-
-    def _assert(self, search_name, param_name):
-        ssm_client = aws_stack.connect_to_service("ssm")
-
-        def do_assert(result):
-            self.assertGreater(len(result), 0)
-            self.assertEqual(param_name, result[0]["Name"])
-            self.assertEqual("123", result[0]["Value"])
-
-        response = ssm_client.get_parameter(Name=search_name)
-        do_assert([response["Parameter"]])
-
-        response = ssm_client.get_parameters(Names=[search_name])
-        do_assert(response["Parameters"])
+            # TODO: AssertionError: assert '/aws/reference/secretsmanager/9763a545_test_secret_params' in ['inexistent_param', '/aws/reference/secretsmanager/inexistent_secret']
+            assert param in ["inexistent_param", secret_path + "inexistent_secret"]
 
-    def test_get_parameters_by_path_and_filter_by_labels(self):
-        ssm_client = aws_stack.connect_to_service("ssm")
-        path = "/my/path"
+    def test_get_parameters_by_path_and_filter_by_labels(self, ssm_client, create_parameter):
+        prefix = f"/prefix-{short_uid()}"
+        path = f"{prefix}/path"
         value = "value"
-        param = ssm_client.put_parameter(Name=path, Value=value, Type="String")
+        param = create_parameter(Name=path, Value=value, Type="String")
         ssm_client.label_parameter_version(
             Name=path, ParameterVersion=param["Version"], Labels=["latest"]
         )
         list_of_params = ssm_client.get_parameters_by_path(
-            Path="/my", ParameterFilters=[{"Key": "Label", "Values": ["latest"]}]
+            Path=prefix, ParameterFilters=[{"Key": "Label", "Values": ["latest"]}]
         )
-        self.assertEqual("/my/path", list_of_params["Parameters"][0]["Name"])
+        assert len(list_of_params["Parameters"]) == 1
+        found_param = list_of_params["Parameters"][0]
+        assert path == found_param["Name"]
+        assert found_param["ARN"]
+        assert found_param["Type"] == "String"
+        assert found_param["Value"] == "value"