Repository: getmoto/moto

Issue:
Failing to match valid Eventbridge pattern
I have a couple of Eventbridge events (`{"root": "child"}` and `{"root": ["child"]}`) which I want to match against pattern `{"root": ["child"]}`.

Both these matches are valid according to `aws events test-event-pattern` -

```
aws events test-event-pattern --event '{"id": 1234567890, "source": "my-source", "detail-type": "my-detail-type", "detail": {"root": "child"}, "account": 1234567890, "region": "eu-west-1", "time": "1970-12-20T20:00:00Z"}' --event-pattern '{"source": ["my-source"], "detail": {"root": ["child"]}}' --output text
True
```

```
aws events test-event-pattern --event '{"id": 1234567890, "source": "my-source", "detail-type": "my-detail-type", "detail": {"root": ["child"]}, "account": 1234567890, "region": "eu-west-1", "time": "1970-12-20T20:00:00Z"}' --event-pattern '{"source": ["my-source"], "detail": {"root": ["child"]}}' --output text
True
```

But I can only get the former pattern to match when I use `moto` to mock the message flow (script at bottom)

```
 % python scripts/demo/moto_events/test_moto.py
{"root": "child"} -> True
{"root": ["child"]} -> False
```

ps using `moto` `2.0.11`

---

```
from moto import mock_events, mock_sqs

from botocore.exceptions import ClientError

import boto3, copy, json, yaml

EventBase=yaml.safe_load("""
Source: my-source
DetailType: my-detail-type
""")

Statement=yaml.safe_load("""
Effect: Allow
Principal:
  Service: events.amazonaws.com
Action: "sqs:SendMessage"
Resource: "*"
""")

def drain_sqs(sqs, queueurl, nmax=100):
    messages, count = [], 0
    while True:
        resp=sqs.receive_message(QueueUrl=queueurl)
        if ("Messages" not in resp or
            count > nmax):
            break
        messages+=resp["Messages"]
        count+=1
    return messages

@mock_events
@mock_sqs
def test_event_pattern(detail,
                       eventbase=EventBase,
                       statement=Statement):
    events, sqs, = boto3.client("events"), boto3.client("sqs")
    policy=json.dumps({"Version": "2012-10-17",
                       "Statement": [statement]})
    queue=sqs.create_queue(QueueName="my-queue",
                           Attributes={"Policy": policy})
    queueurl=queue["QueueUrl"]
    queueattrs=sqs.get_queue_attributes(QueueUrl=queueurl)["Attributes"]
    queuearn=queueattrs["QueueArn"]
    pattern={"detail": detail["pattern"]}
    events.put_rule(Name="my-rule",
                    State="ENABLED",
                    EventPattern=json.dumps(pattern))
    events.put_targets(Rule="my-rule",
                       Targets=[{"Id": "my-target-id",
                                 "Arn": queuearn}])
    event=copy.deepcopy(eventbase)
    event["Detail"]=json.dumps(detail["event"])
    events.put_events(Entries=[event])
    return drain_sqs(sqs, queue["QueueUrl"])

if __name__=="__main__":
    try:
        for detail in [{"event": {"root": "child"},
                        "pattern": {"root": ["child"]}},
                       {"event": {"root": ["child"]},
                        "pattern": {"root": ["child"]}}]:
            messages=test_event_pattern(detail)
            print ("%s -> %s" % (json.dumps(detail["event"]),
                                 bool(len(messages))))
    except ClientError as error:
        print ("Error: %s" % str(error))
    
```


Discussion:
Thanks for raising this here @jhw. Doesn't look like Moto is expecting an event that contains a list - marking it as an enhancement to deal with this properly.

Resolving patch:
diff --git a/moto/events/models.py b/moto/events/models.py
--- a/moto/events/models.py
+++ b/moto/events/models.py
@@ -838,12 +838,13 @@ def _does_event_match(self, event, pattern):
     def _does_item_match_filters(self, item, filters):
         allowed_values = [value for value in filters if isinstance(value, str)]
         allowed_values_match = item in allowed_values if allowed_values else True
+        full_match = isinstance(item, list) and item == allowed_values
         named_filter_matches = [
             self._does_item_match_named_filter(item, pattern)
             for pattern in filters
             if isinstance(pattern, dict)
         ]
-        return allowed_values_match and all(named_filter_matches)
+        return (full_match or allowed_values_match) and all(named_filter_matches)
 
     @staticmethod
     def _does_item_match_named_filter(item, pattern):


Test patch:
diff --git a/tests/test_events/test_event_pattern.py b/tests/test_events/test_event_pattern.py
--- a/tests/test_events/test_event_pattern.py
+++ b/tests/test_events/test_event_pattern.py
@@ -16,6 +16,8 @@ def test_event_pattern_with_nested_event_filter():
     pattern = EventPattern.load(json.dumps({"detail": {"foo": ["bar"]}}))
     assert pattern.matches_event({"detail": {"foo": "bar"}})
     assert not pattern.matches_event({"detail": {"foo": "baz"}})
+    # The full list should match as well
+    assert pattern.matches_event({"detail": {"foo": ["bar"]}})
 
 
 def test_event_pattern_with_exists_event_filter():