Skip to content

Commit 0c63078

Browse files
Alex Wangwangyb-A
authored andcommitted
fix(testing-sdk): fix serilization for http response
- Use cuszomized serilizer instead of AWS boto one, because previous one does not support the serialization from Object -> Response json - Update unit tests
1 parent d5e3feb commit 0c63078

File tree

4 files changed

+256
-125
lines changed

4 files changed

+256
-125
lines changed

src/aws_durable_execution_sdk_python_testing/web/models.py

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
from aws_durable_execution_sdk_python_testing.web.routes import Route
1717
from aws_durable_execution_sdk_python_testing.web.serialization import (
1818
AwsRestJsonDeserializer,
19-
AwsRestJsonSerializer,
19+
JSONSerializer,
20+
Serializer,
2021
)
2122

2223

@@ -146,54 +147,20 @@ class HTTPResponse:
146147
status_code: int
147148
headers: dict[str, str]
148149
body: dict[str, Any]
150+
serializer: Serializer = JSONSerializer()
149151

150-
def body_to_bytes(self, operation_name: str | None = None) -> bytes:
152+
def body_to_bytes(self) -> bytes:
151153
"""Convert response dict body to bytes for HTTP transmission.
152154
153-
Args:
154-
operation_name: Optional AWS operation name for boto serialization
155-
156155
Returns:
157156
bytes: Serialized response body
158157
159158
Raises:
160159
InvalidParameterValueException: If serialization fails with both AWS and JSON methods
161160
"""
162-
# Try AWS serialization first if operation_name provided
163-
if operation_name:
164-
try:
165-
serializer = AwsRestJsonSerializer.create(operation_name)
166-
result = serializer.to_bytes(self.body)
167-
logger.debug(
168-
"Successfully serialized response using AWS serializer for %s",
169-
operation_name,
170-
)
171-
return result # noqa: TRY300
172-
except InvalidParameterValueException as e:
173-
logger.warning(
174-
"AWS serialization failed for %s, falling back to JSON: %s",
175-
operation_name,
176-
e,
177-
)
178-
# Fall back to standard JSON
179-
try:
180-
result = json.dumps(self.body, separators=(",", ":")).encode(
181-
"utf-8"
182-
)
183-
logger.debug("Successfully serialized response using JSON fallback")
184-
return result # noqa: TRY300
185-
except (TypeError, ValueError) as json_error:
186-
msg = f"Both AWS and JSON serialization failed: AWS error: {e}, JSON error: {json_error}"
187-
raise InvalidParameterValueException(msg) from json_error
188-
else:
189-
# Use standard JSON serialization
190-
try:
191-
result = json.dumps(self.body, separators=(",", ":")).encode("utf-8")
192-
logger.debug("Successfully serialized response using standard JSON")
193-
return result # noqa: TRY300
194-
except (TypeError, ValueError) as e:
195-
msg = f"JSON serialization failed: {e}"
196-
raise InvalidParameterValueException(msg) from e
161+
result = self.serializer.to_bytes(data=self.body)
162+
logger.debug("Serialized result - before: %s, after: %s", self.body, result)
163+
return result
197164

198165
@classmethod
199166
def from_dict(

src/aws_durable_execution_sdk_python_testing/web/serialization.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import json
1010
import os
1111
from typing import Any, Protocol
12+
from datetime import datetime
1213

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

5960

61+
class JSONSerializer:
62+
"""JSON serializer with datetime support."""
63+
64+
def to_bytes(self, data: Any) -> bytes:
65+
"""Serialize data to JSON bytes."""
66+
try:
67+
json_string = json.dumps(
68+
data, separators=(",", ":"), default=self._default_handler
69+
)
70+
return json_string.encode("utf-8")
71+
except (TypeError, ValueError) as e:
72+
raise InvalidParameterValueException(
73+
f"Failed to serialize data to JSON: {str(e)}"
74+
)
75+
76+
def _default_handler(self, obj: Any) -> str:
77+
"""Handle non-permitive objects."""
78+
if isinstance(obj, datetime):
79+
return obj.isoformat()
80+
# Raise TypeError for unsupported types
81+
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
82+
83+
6084
class AwsRestJsonSerializer:
6185
"""AWS rest-json serializer using boto."""
6286

tests/web/models_test.py

Lines changed: 31 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,27 @@ def test_http_request_from_bytes_standard_json() -> None:
260260
assert request.body == test_data
261261

262262

263+
def test_http_get_request_from_bytes_ignore_body() -> None:
264+
"""Test HTTPRequest.from_bytes with standard JSON deserialization."""
265+
test_data = {"key": "value", "number": 42}
266+
body_bytes = json.dumps(test_data).encode("utf-8")
267+
268+
path = Route.from_string("/test")
269+
request = HTTPRequest.from_bytes(
270+
body_bytes=body_bytes,
271+
method="GET",
272+
path=path,
273+
headers={"Content-Type": "application/json"},
274+
query_params={"param": ["value"]},
275+
)
276+
277+
assert request.method == "GET"
278+
assert request.path == path
279+
assert request.headers == {"Content-Type": "application/json"}
280+
assert request.query_params == {"param": ["value"]}
281+
assert request.body == {}
282+
283+
263284
def test_http_request_from_bytes_minimal_params() -> None:
264285
"""Test HTTPRequest.from_bytes with minimal parameters."""
265286
test_data = {"message": "hello"}
@@ -413,33 +434,6 @@ def test_http_response_body_to_bytes_compact_format() -> None:
413434
assert "\n" not in body_str # No newlines
414435

415436

416-
def test_http_response_body_to_bytes_aws_operation_fallback() -> None:
417-
"""Test body_to_bytes with AWS operation that falls back to JSON."""
418-
test_data = {"ExecutionId": "test-execution-id", "Status": "SUCCEEDED"}
419-
response = HTTPResponse(status_code=200, headers={}, body=test_data)
420-
421-
# Use a non-existent operation name to trigger fallback
422-
body_bytes = response.body_to_bytes(operation_name="NonExistentOperation")
423-
424-
# Should still work via JSON fallback
425-
assert isinstance(body_bytes, bytes)
426-
parsed_data = json.loads(body_bytes.decode("utf-8"))
427-
assert parsed_data == test_data
428-
429-
430-
def test_http_response_body_to_bytes_invalid_data() -> None:
431-
"""Test body_to_bytes with data that can't be JSON serialized."""
432-
# Create data with non-serializable object
433-
434-
test_data = {"timestamp": datetime.datetime.now(datetime.UTC)}
435-
response = HTTPResponse(status_code=200, headers={}, body=test_data)
436-
437-
with pytest.raises(
438-
InvalidParameterValueException, match="JSON serialization failed"
439-
):
440-
response.body_to_bytes()
441-
442-
443437
def test_http_response_body_to_bytes_empty_body() -> None:
444438
"""Test body_to_bytes with empty body."""
445439
response = HTTPResponse(status_code=204, headers={}, body={})
@@ -465,28 +459,6 @@ def test_http_response_body_to_bytes_complex_data() -> None:
465459
assert parsed_data == complex_data
466460

467461

468-
def test_http_response_body_to_bytes_aws_operation_success() -> None:
469-
"""Test body_to_bytes with valid AWS operation (if available)."""
470-
# This test will use AWS serialization if available, otherwise fall back to JSON
471-
test_data = {
472-
"ExecutionId": "test-execution-id",
473-
"Status": "SUCCEEDED",
474-
"Result": "test-result",
475-
}
476-
response = HTTPResponse(status_code=200, headers={}, body=test_data)
477-
478-
# Try with a real AWS operation name
479-
body_bytes = response.body_to_bytes(operation_name="StartDurableExecution")
480-
481-
# Should get valid bytes regardless of AWS vs JSON serialization
482-
assert isinstance(body_bytes, bytes)
483-
assert len(body_bytes) > 0
484-
485-
# Should be valid JSON (either from AWS serialization or fallback)
486-
parsed_data = json.loads(body_bytes.decode("utf-8"))
487-
assert isinstance(parsed_data, dict)
488-
489-
490462
# Tests for HTTPResponse.from_dict method
491463

492464

@@ -627,47 +599,21 @@ def test_http_request_from_bytes_aws_deserialization_fallback_error() -> None:
627599
)
628600

629601

630-
def test_http_response_body_to_bytes_aws_serialization_success() -> None:
631-
"""Test HTTPResponse.body_to_bytes with successful AWS serialization."""
632-
633-
test_data = {"ExecutionId": "test-id", "Status": "SUCCEEDED"}
634-
response = HTTPResponse(status_code=200, headers={}, body=test_data)
635-
expected_bytes = b'{"ExecutionId":"test-id","Status":"SUCCEEDED"}'
636-
637-
# Mock successful AWS serialization
638-
mock_serializer = Mock()
639-
mock_serializer.to_bytes.return_value = expected_bytes
640-
641-
with patch(
642-
"aws_durable_execution_sdk_python_testing.web.models.AwsRestJsonSerializer.create",
643-
return_value=mock_serializer,
644-
):
645-
result = response.body_to_bytes(operation_name="StartDurableExecution")
646-
647-
assert result == expected_bytes
648-
mock_serializer.to_bytes.assert_called_once_with(test_data)
649-
650-
651-
def test_http_response_body_to_bytes_aws_serialization_fallback_error() -> None:
652-
"""Test HTTPResponse.body_to_bytes when both AWS and JSON serialization fail."""
602+
def test_http_response_body_to_bytes_serialization_error() -> None:
603+
"""Test HTTPResponse.body_to_bytes when JSON serialization fail."""
653604

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

658-
# Mock AWS serialization failure
659-
mock_serializer = Mock()
660-
mock_serializer.to_bytes.side_effect = InvalidParameterValueException("AWS failed")
609+
test_data = {"custom": CustomObject()}
610+
response = HTTPResponse(status_code=200, headers={}, body=test_data)
661611

662-
with patch(
663-
"aws_durable_execution_sdk_python_testing.web.models.AwsRestJsonSerializer.create",
664-
return_value=mock_serializer,
612+
with pytest.raises(
613+
InvalidParameterValueException,
614+
match="Failed to serialize data to JSON: Object of type CustomObject is not JSON serializable",
665615
):
666-
with pytest.raises(
667-
InvalidParameterValueException,
668-
match="Both AWS and JSON serialization failed",
669-
):
670-
response.body_to_bytes(operation_name="StartDurableExecution")
616+
response.body_to_bytes()
671617

672618

673619
# Tests for HTTPResponse.create_error_from_exception method

0 commit comments

Comments
 (0)