Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
47 changes: 7 additions & 40 deletions src/aws_durable_execution_sdk_python_testing/web/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
from aws_durable_execution_sdk_python_testing.web.routes import Route
from aws_durable_execution_sdk_python_testing.web.serialization import (
AwsRestJsonDeserializer,
AwsRestJsonSerializer,
JSONSerializer,
Serializer,
)


Expand Down Expand Up @@ -146,54 +147,20 @@ class HTTPResponse:
status_code: int
headers: dict[str, str]
body: dict[str, Any]
serializer: Serializer = JSONSerializer()

def body_to_bytes(self, operation_name: str | None = None) -> bytes:
def body_to_bytes(self) -> bytes:
"""Convert response dict body to bytes for HTTP transmission.

Args:
operation_name: Optional AWS operation name for boto serialization

Returns:
bytes: Serialized response body

Raises:
InvalidParameterValueException: If serialization fails with both AWS and JSON methods
"""
# Try AWS serialization first if operation_name provided
if operation_name:
try:
serializer = AwsRestJsonSerializer.create(operation_name)
result = serializer.to_bytes(self.body)
logger.debug(
"Successfully serialized response using AWS serializer for %s",
operation_name,
)
return result # noqa: TRY300
except InvalidParameterValueException as e:
logger.warning(
"AWS serialization failed for %s, falling back to JSON: %s",
operation_name,
e,
)
# Fall back to standard JSON
try:
result = json.dumps(self.body, separators=(",", ":")).encode(
"utf-8"
)
logger.debug("Successfully serialized response using JSON fallback")
return result # noqa: TRY300
except (TypeError, ValueError) as json_error:
msg = f"Both AWS and JSON serialization failed: AWS error: {e}, JSON error: {json_error}"
raise InvalidParameterValueException(msg) from json_error
else:
# Use standard JSON serialization
try:
result = json.dumps(self.body, separators=(",", ":")).encode("utf-8")
logger.debug("Successfully serialized response using standard JSON")
return result # noqa: TRY300
except (TypeError, ValueError) as e:
msg = f"JSON serialization failed: {e}"
raise InvalidParameterValueException(msg) from e
result = self.serializer.to_bytes(data=self.body)
logger.debug("Serialized result - before: %s, after: %s", self.body, result)
return result

@classmethod
def from_dict(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import json
import os
from typing import Any, Protocol
from datetime import datetime

import aws_durable_execution_sdk_python
import botocore.loaders # type: ignore
Expand Down Expand Up @@ -57,6 +58,29 @@ def from_bytes(self, data: bytes) -> dict[str, Any]:
... # pragma: no cover


class JSONSerializer:
"""JSON serializer with datetime support."""

def to_bytes(self, data: Any) -> bytes:
"""Serialize data to JSON bytes."""
try:
json_string = json.dumps(
data, separators=(",", ":"), default=self._default_handler
)
return json_string.encode("utf-8")
except (TypeError, ValueError) as e:
raise InvalidParameterValueException(
f"Failed to serialize data to JSON: {str(e)}"
)

def _default_handler(self, obj: Any) -> str:
"""Handle non-permitive objects."""
if isinstance(obj, datetime):
return obj.isoformat()
# Raise TypeError for unsupported types
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")


class AwsRestJsonSerializer:
"""AWS rest-json serializer using boto."""

Expand Down
116 changes: 31 additions & 85 deletions tests/web/models_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,27 @@ def test_http_request_from_bytes_standard_json() -> None:
assert request.body == test_data


def test_http_get_request_from_bytes_ignore_body() -> None:
"""Test HTTPRequest.from_bytes with standard JSON deserialization."""
test_data = {"key": "value", "number": 42}
body_bytes = json.dumps(test_data).encode("utf-8")

path = Route.from_string("/test")
request = HTTPRequest.from_bytes(
body_bytes=body_bytes,
method="GET",
path=path,
headers={"Content-Type": "application/json"},
query_params={"param": ["value"]},
)

assert request.method == "GET"
assert request.path == path
assert request.headers == {"Content-Type": "application/json"}
assert request.query_params == {"param": ["value"]}
assert request.body == {}


def test_http_request_from_bytes_minimal_params() -> None:
"""Test HTTPRequest.from_bytes with minimal parameters."""
test_data = {"message": "hello"}
Expand Down Expand Up @@ -413,33 +434,6 @@ def test_http_response_body_to_bytes_compact_format() -> None:
assert "\n" not in body_str # No newlines


def test_http_response_body_to_bytes_aws_operation_fallback() -> None:
"""Test body_to_bytes with AWS operation that falls back to JSON."""
test_data = {"ExecutionId": "test-execution-id", "Status": "SUCCEEDED"}
response = HTTPResponse(status_code=200, headers={}, body=test_data)

# Use a non-existent operation name to trigger fallback
body_bytes = response.body_to_bytes(operation_name="NonExistentOperation")

# Should still work via JSON fallback
assert isinstance(body_bytes, bytes)
parsed_data = json.loads(body_bytes.decode("utf-8"))
assert parsed_data == test_data


def test_http_response_body_to_bytes_invalid_data() -> None:
"""Test body_to_bytes with data that can't be JSON serialized."""
# Create data with non-serializable object

test_data = {"timestamp": datetime.datetime.now(datetime.UTC)}
response = HTTPResponse(status_code=200, headers={}, body=test_data)

with pytest.raises(
InvalidParameterValueException, match="JSON serialization failed"
):
response.body_to_bytes()


def test_http_response_body_to_bytes_empty_body() -> None:
"""Test body_to_bytes with empty body."""
response = HTTPResponse(status_code=204, headers={}, body={})
Expand All @@ -465,28 +459,6 @@ def test_http_response_body_to_bytes_complex_data() -> None:
assert parsed_data == complex_data


def test_http_response_body_to_bytes_aws_operation_success() -> None:
"""Test body_to_bytes with valid AWS operation (if available)."""
# This test will use AWS serialization if available, otherwise fall back to JSON
test_data = {
"ExecutionId": "test-execution-id",
"Status": "SUCCEEDED",
"Result": "test-result",
}
response = HTTPResponse(status_code=200, headers={}, body=test_data)

# Try with a real AWS operation name
body_bytes = response.body_to_bytes(operation_name="StartDurableExecution")

# Should get valid bytes regardless of AWS vs JSON serialization
assert isinstance(body_bytes, bytes)
assert len(body_bytes) > 0

# Should be valid JSON (either from AWS serialization or fallback)
parsed_data = json.loads(body_bytes.decode("utf-8"))
assert isinstance(parsed_data, dict)


# Tests for HTTPResponse.from_dict method


Expand Down Expand Up @@ -627,47 +599,21 @@ def test_http_request_from_bytes_aws_deserialization_fallback_error() -> None:
)


def test_http_response_body_to_bytes_aws_serialization_success() -> None:
"""Test HTTPResponse.body_to_bytes with successful AWS serialization."""

test_data = {"ExecutionId": "test-id", "Status": "SUCCEEDED"}
response = HTTPResponse(status_code=200, headers={}, body=test_data)
expected_bytes = b'{"ExecutionId":"test-id","Status":"SUCCEEDED"}'

# Mock successful AWS serialization
mock_serializer = Mock()
mock_serializer.to_bytes.return_value = expected_bytes

with patch(
"aws_durable_execution_sdk_python_testing.web.models.AwsRestJsonSerializer.create",
return_value=mock_serializer,
):
result = response.body_to_bytes(operation_name="StartDurableExecution")

assert result == expected_bytes
mock_serializer.to_bytes.assert_called_once_with(test_data)


def test_http_response_body_to_bytes_aws_serialization_fallback_error() -> None:
"""Test HTTPResponse.body_to_bytes when both AWS and JSON serialization fail."""
def test_http_response_body_to_bytes_serialization_error() -> None:
"""Test HTTPResponse.body_to_bytes when JSON serialization fail."""

# Create data that can't be JSON serialized
test_data = {"timestamp": datetime.datetime.now(datetime.UTC)}
response = HTTPResponse(status_code=200, headers={}, body=test_data)
class CustomObject:
pass

# Mock AWS serialization failure
mock_serializer = Mock()
mock_serializer.to_bytes.side_effect = InvalidParameterValueException("AWS failed")
test_data = {"custom": CustomObject()}
response = HTTPResponse(status_code=200, headers={}, body=test_data)

with patch(
"aws_durable_execution_sdk_python_testing.web.models.AwsRestJsonSerializer.create",
return_value=mock_serializer,
with pytest.raises(
InvalidParameterValueException,
match="Failed to serialize data to JSON: Object of type CustomObject is not JSON serializable",
):
with pytest.raises(
InvalidParameterValueException,
match="Both AWS and JSON serialization failed",
):
response.body_to_bytes(operation_name="StartDurableExecution")
response.body_to_bytes()


# Tests for HTTPResponse.create_error_from_exception method
Expand Down
Loading
Loading