Repository: localstack/localstack

Issue:
SQS to Lambda trigger : lambda is triggered by messages in the wrong SQS queue
I have a couple of queues:
`replay`
`replay-request`
`incoming-events`

I have a lambda function:
`replay`

The lambda function is triggered by `replay-request` queue and the event source mapping for the `replay` lambda function looks like this:
```
{
    "EventSourceMappings": [
        {
            "UUID": "af897afd-e186-4875-a6de-224a35f47413",
            "BatchSize": 100,
            "EventSourceArn": "arn:aws:sqs:elasticmq:000000000000:local-event-routing-service-replay-request",
            "FunctionArn": "arn:aws:lambda:us-west-2:000000000000:function:local-event-routing-service-replay",
            "LastModified": 1572479974.0,
            "LastProcessingResult": "OK",
            "State": "Enabled",
            "StateTransitionReason": "User action"
        }
    ]
}
```

When a message is published to the `replay-request` queue, the lambda function is invoked, as expected.

The problem is, when a message is published to the `replay` queue, the lambda function is invoked, even though there is no event source mapping for this queue.

If I publish to other queues (for example, `incoming-events`, the lambda function is not invoked (as expected).

Expected behavior is that ONLY the queue specified in the event source mapping will invoke the lambda function.


Discussion:

Resolving patch:
diff --git a/localstack/services/awslambda/lambda_api.py b/localstack/services/awslambda/lambda_api.py
--- a/localstack/services/awslambda/lambda_api.py
+++ b/localstack/services/awslambda/lambda_api.py
@@ -284,11 +284,27 @@ def get_event_sources(func_name=None, source_arn=None):
     result = []
     for m in event_source_mappings:
         if not func_name or (m['FunctionArn'] in [func_name, func_arn(func_name)]):
-            if not source_arn or (m['EventSourceArn'].startswith(source_arn)):
+            if _arn_match(mapped=m['EventSourceArn'], occurred=source_arn):
                 result.append(m)
     return result
 
 
+def _arn_match(mapped, occurred):
+    if not occurred or mapped == occurred:
+        return True
+    # Some types of ARNs can end with a path separated by slashes, for
+    # example the ARN of a DynamoDB stream is tableARN/stream/ID.  It's
+    # a little counterintuitive that a more specific mapped ARN can
+    # match a less specific ARN on the event, but some integration tests
+    # rely on it for things like subscribing to a stream and matching an
+    # event labeled with the table ARN.
+    elif mapped.startswith(occurred):
+        suffix = mapped[len(occurred):]
+        return suffix[0] == '/'
+    else:
+        return False
+
+
 def get_function_version(arn, version):
     func = arn_to_lambda.get(arn)
     return format_func_details(func, version=version, always_add_version=True)


Test patch:
diff --git a/tests/unit/test_lambda.py b/tests/unit/test_lambda.py
--- a/tests/unit/test_lambda.py
+++ b/tests/unit/test_lambda.py
@@ -49,6 +49,37 @@ def test_get_event_source_mapping(self):
             result = lambda_api.get_event_source_mapping(self.TEST_UUID)
             self.assertEqual(json.loads(result.get_data()).get('UUID'), self.TEST_UUID)
 
+    def test_get_event_sources(self):
+        with self.app.test_request_context():
+            lambda_api.event_source_mappings.append(
+                {
+                    'UUID': self.TEST_UUID,
+                    'EventSourceArn': 'the_arn'
+                })
+
+            # Match source ARN
+            result = lambda_api.get_event_sources(source_arn='the_arn')
+            self.assertEqual(len(result), 1)
+            self.assertEqual(result[0].get('UUID'), self.TEST_UUID)
+
+            # No partial match on source ARN
+            result = lambda_api.get_event_sources(source_arn='the_')
+            self.assertEqual(len(result), 0)
+
+    def test_get_event_sources_with_paths(self):
+        with self.app.test_request_context():
+            lambda_api.event_source_mappings.append(
+                {
+                    'UUID': self.TEST_UUID,
+                    'EventSourceArn': 'the_arn/path/subpath'
+                })
+
+            # Do partial match on paths
+            result = lambda_api.get_event_sources(source_arn='the_arn')
+            self.assertEqual(len(result), 1)
+            result = lambda_api.get_event_sources(source_arn='the_arn/path')
+            self.assertEqual(len(result), 1)
+
     def test_delete_event_source_mapping(self):
         with self.app.test_request_context():
             lambda_api.event_source_mappings.append({'UUID': self.TEST_UUID})