8000 Added test and fix for wrong time format and serialization in events v2 by zaingz · Pull Request #11959 · localstack/localstack · GitHub
[go: up one dir, main page]

Skip to content

Added test and fix for wrong time format and serialization in events v2 #11959

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions localstack-core/localstack/aws/api/events/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime
from enum import StrEnum
from typing import Dict, List, Optional, TypedDict
from typing import Dict, List, Optional, TypedDict, Union

from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler

Expand Down Expand Up @@ -919,7 +919,7 @@ class EventSource(TypedDict, total=False):


EventSourceList = List[EventSource]
EventTime = datetime
EventTime = Union[datetime, str]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use the newer recommended way datetime | str

HeaderParametersMap = Dict[HeaderKey, HeaderValue]
QueryStringParametersMap = Dict[QueryStringKey, QueryStringValue]
PathParameterList = List[PathParameter]
Expand Down
10 changes: 9 additions & 1 deletion localstack-core/localstack/services/events/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ def get_event_time(event: PutEventsRequestEntry) -> EventTime:
return event_time


def format_event_time(event_time: datetime) -> str:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would we need the same function 2 times? just use event_time_to_time_string ?

"""Format datetime object to AWS EventBridge format."""
return event_time.strftime("%Y-%m-%dT%H:%M:%SZ")


def event_time_to_time_string(event_time: EventTime) -> str:
return event_time.strftime("%Y-%m-%dT%H:%M:%SZ")

Expand Down Expand Up @@ -181,14 +186,17 @@ def format_event(
message_id = message.get("original_id", str(long_uid()))
region = message.get("original_region", region)
account_id = message.get("original_account", account_id)
# Format the datetime to ISO-8601 string
event_time = get_event_time(event)
formatted_time = format_event_time(event_time)

formatted_event = {
"version": "0",
"id": message_id,
"detail-type": event.get("DetailType"),
"source": event.get("Source"),
"account": account_id,
"time": get_event_time(event),
"time": formatted_time,
"region": region,
"resources": event.get("Resources", []),
"detail": json.loads(event.get("Detail", "{}")),
Expand Down
64 changes: 64 additions & 0 deletions tests/aws/services/events/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
"""

import base64
import datetime
import json
import os
import re
import time
import uuid

Expand Down Expand Up @@ -578,6 +580,68 @@ def test_put_events_with_target_delivery_failure(

assert len(messages) == 0, "No messages should be delivered when queue doesn't exist"

@markers.aws.validated
@pytest.mark.skipif(is_old_provider(), reason="Test specific for v2 provider")
def test_put_events_with_time_field(
self, events_put_rule, create_sqs_events_target, aws_client, snapshot
):
"""Test that EventBridge correctly handles datetime serialization in events."""
rule_name = f"test-rule-{short_uid()}"
queue_url, queue_arn = create_sqs_events_target()

snapshot.add_transformers_list(
[
snapshot.transform.key_value("MD5OfBody", reference_replacement=False),
*snapshot.transform.sqs_api(),
]
)

events_put_rule(
Name=rule_name,
EventPattern=json.dumps(
{"source": ["test-source"], "detail-type": ["test-detail-type"]}
),
)

aws_client.events.put_targets(Rule=rule_name, Targets=[{"Id": "id1", "Arn": queue_arn}])

timestamp = datetime.datetime.utcnow()
event = {
"Source": "test-source",
"DetailType": "test-detail-type",
"Time": timestamp,
"Detail": json.dumps({"message": "test message"}),
}

response = aws_client.events.put_events(Entries=[event])
snapshot.match("put-events", response)

messages = sqs_collect_messages(aws_client, queue_url, expected_events_count=1)
assert len(messages) == 1
snapshot.match("sqs-messages", messages)

received_event = json.loads(messages[0]["Body"])
# Explicit assertions for time field format GH issue: https://github.com/localstack/localstack/issues/11630#issuecomment-2506187279
assert "time" in received_event, "Time field missing in the event"
time_str = received_event["time"]

# Verify ISO8601 format: YYYY-MM-DDThh:mm:ssZ
# Example: "2024-11-28T13:44:36Z"
assert re.match(
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$", time_str
), f"Time field '{time_str}' does not match ISO8601 format (YYYY-MM-DDThh:mm:ssZ)"

# Verify we can parse it back to datetime
datetime_obj = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%SZ")
assert isinstance(
datetime_obj, datetime.datetime
), f"Failed to parse time string '{time_str}' back to datetime object"

time_difference = abs((datetime_obj - timestamp.replace(microsecond=0)).total_seconds())
assert (
time_difference <= 60
), f"Time in event '{time_str}' differs too much from sent time '{timestamp.isoformat()}'"


class TestEventBus:
@markers.aws.validated
Expand Down
37 changes: 37 additions & 0 deletions tests/aws/services/events/test_events.snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -2695,5 +2695,42 @@
}
}
}
},
"tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_time_field": {
"recorded-date": "28-11-2024, 21:25:00",
"recorded-content": {
"put-events": {
"Entries": [
{
"EventId": "<uuid:1>"
}
],
"FailedEntryCount": 0,
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"sqs-messages": [
{
"MessageId": "<uuid:2>",
"ReceiptHandle": "<receipt-handle:1>",
"MD5OfBody": "m-d5-of-body",
"Body": {
"version": "0",
"id": "<uuid:1>",
"detail-type": "test-detail-type",
"source": "test-source",
"account": "111111111111",
"time": "date",
"region": "<region>",
"resources": [],
"detail": {
"message": "test message"
}
}
}
]
}
}
}
3 changes: 3 additions & 0 deletions tests/aws/services/events/test_events.validation.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@
"tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_target_delivery_failure": {
"last_validated_date": "2024-11-20T17:19:19+00:00"
},
"tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_time_field": {
"last_validated_date": "2024-11-28T21:25:00+00:00"
},
"tests/aws/services/events/test_events.py::TestEvents::test_put_events_without_source": {
"last_validated_date": "2024-06-19T10:40:50+00:00"
}
Expand Down
Loading
0