Skip to content

Commit 8264fb1

Browse files
Fix tests
1 parent 65d9454 commit 8264fb1

File tree

2 files changed

+185
-157
lines changed

2 files changed

+185
-157
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
"""
2+
Test for issue #8065: @idempotent_function fails with non-JSON-serializable types in Pydantic models
3+
4+
Bug: _prepare_data() calls model_dump() without mode="json", which doesn't
5+
serialize UUIDs, dates, datetimes, Decimals, and Paths to JSON-compatible strings.
6+
"""
7+
8+
from datetime import date, datetime
9+
from decimal import Decimal
10+
from pathlib import PurePosixPath
11+
from uuid import UUID
12+
13+
from pydantic import BaseModel
14+
15+
from aws_lambda_powertools.utilities.idempotency import (
16+
IdempotencyConfig,
17+
idempotent_function,
18+
)
19+
from aws_lambda_powertools.utilities.idempotency.persistence.base import (
20+
BasePersistenceLayer,
21+
DataRecord,
22+
)
23+
from tests.functional.idempotency.utils import hash_idempotency_key
24+
25+
TESTS_MODULE_PREFIX = "test-func.tests.functional.idempotency._pydantic.test_idempotency_pydantic_json_serialization"
26+
27+
28+
class MockPersistenceLayer(BasePersistenceLayer):
29+
def __init__(self, expected_idempotency_key: str):
30+
self.expected_idempotency_key = expected_idempotency_key
31+
super().__init__()
32+
33+
def _put_record(self, data_record: DataRecord) -> None:
34+
assert data_record.idempotency_key == self.expected_idempotency_key
35+
36+
def _update_record(self, data_record: DataRecord) -> None:
37+
assert data_record.idempotency_key == self.expected_idempotency_key
38+
39+
def _get_record(self, idempotency_key) -> DataRecord: ...
40+
41+
def _delete_record(self, data_record: DataRecord) -> None: ...
42+
43+
44+
# --- Models ---
45+
46+
47+
class PaymentWithUUID(BaseModel):
48+
payment_id: UUID
49+
customer_id: str
50+
51+
52+
class EventWithDate(BaseModel):
53+
event_id: str
54+
event_date: date
55+
56+
57+
class OrderWithDatetime(BaseModel):
58+
order_id: str
59+
created_at: datetime
60+
61+
62+
class InvoiceWithDecimal(BaseModel):
63+
invoice_id: str
64+
amount: Decimal
65+
66+
67+
class ConfigWithPath(BaseModel):
68+
config_id: str
69+
file_path: PurePosixPath
70+
71+
72+
def test_idempotent_function_with_uuid():
73+
# GIVEN
74+
config = IdempotencyConfig(use_local_cache=True)
75+
payment_uuid = UUID("12345678-1234-5678-1234-567812345678")
76+
mock_event = {"payment_id": str(payment_uuid), "customer_id": "c-456"}
77+
idempotency_key = f"{TESTS_MODULE_PREFIX}.test_idempotent_function_with_uuid.<locals>.collect_payment#{hash_idempotency_key(mock_event)}" # noqa E501
78+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
79+
80+
@idempotent_function(
81+
data_keyword_argument="payment",
82+
persistence_store=persistence_layer,
83+
config=config,
84+
)
85+
def collect_payment(payment: PaymentWithUUID) -> dict:
86+
return {"status": "ok"}
87+
88+
# WHEN
89+
payment = PaymentWithUUID(payment_id=payment_uuid, customer_id="c-456")
90+
result = collect_payment(payment=payment)
91+
92+
# THEN
93+
assert result == {"status": "ok"}
94+
95+
96+
def test_idempotent_function_with_date():
97+
# GIVEN
98+
config = IdempotencyConfig(use_local_cache=True)
99+
mock_event = {"event_id": "evt-001", "event_date": "2024-03-20"}
100+
idempotency_key = f"{TESTS_MODULE_PREFIX}.test_idempotent_function_with_date.<locals>.process_event#{hash_idempotency_key(mock_event)}" # noqa E501
101+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
102+
103+
@idempotent_function(
104+
data_keyword_argument="event",
105+
persistence_store=persistence_layer,
106+
config=config,
107+
)
108+
def process_event(event: EventWithDate) -> dict:
109+
return {"status": "ok"}
110+
111+
# WHEN
112+
event = EventWithDate(event_id="evt-001", event_date=date(2024, 3, 20))
113+
result = process_event(event=event)
114+
115+
# THEN
116+
assert result == {"status": "ok"}
117+
118+
119+
def test_idempotent_function_with_datetime():
120+
# GIVEN
121+
config = IdempotencyConfig(use_local_cache=True)
122+
mock_event = {"order_id": "ord-001", "created_at": "2024-03-20T14:30:00"}
123+
idempotency_key = f"{TESTS_MODULE_PREFIX}.test_idempotent_function_with_datetime.<locals>.process_order#{hash_idempotency_key(mock_event)}" # noqa E501
124+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
125+
126+
@idempotent_function(
127+
data_keyword_argument="order",
128+
persistence_store=persistence_layer,
129+
config=config,
130+
)
131+
def process_order(order: OrderWithDatetime) -> dict:
132+
return {"status": "ok"}
133+
134+
# WHEN
135+
order = OrderWithDatetime(order_id="ord-001", created_at=datetime(2024, 3, 20, 14, 30, 0))
136+
result = process_order(order=order)
137+
138+
# THEN
139+
assert result == {"status": "ok"}
140+
141+
142+
def test_idempotent_function_with_decimal():
143+
# GIVEN
144+
config = IdempotencyConfig(use_local_cache=True)
145+
mock_event = {"invoice_id": "inv-001", "amount": "199.99"}
146+
idempotency_key = f"{TESTS_MODULE_PREFIX}.test_idempotent_function_with_decimal.<locals>.process_invoice#{hash_idempotency_key(mock_event)}" # noqa E501
147+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
148+
149+
@idempotent_function(
150+
data_keyword_argument="invoice",
151+
persistence_store=persistence_layer,
152+
config=config,
153+
)
154+
def process_invoice(invoice: InvoiceWithDecimal) -> dict:
155+
return {"status": "ok"}
156+
157+
# WHEN
158+
invoice = InvoiceWithDecimal(invoice_id="inv-001", amount=Decimal("199.99"))
159+
result = process_invoice(invoice=invoice)
160+
161+
# THEN
162+
assert result == {"status": "ok"}
163+
164+
165+
def test_idempotent_function_with_path():
166+
# GIVEN
167+
config = IdempotencyConfig(use_local_cache=True)
168+
mock_event = {"config_id": "cfg-001", "file_path": "/etc/app/config.yaml"}
169+
idempotency_key = f"{TESTS_MODULE_PREFIX}.test_idempotent_function_with_path.<locals>.process_config#{hash_idempotency_key(mock_event)}" # noqa E501
170+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
171+
172+
@idempotent_function(
173+
data_keyword_argument="config",
174+
persistence_store=persistence_layer,
175+
config=config,
176+
)
177+
def process_config(config: ConfigWithPath) -> dict:
178+
return {"status": "ok"}
179+
180+
# WHEN
181+
cfg = ConfigWithPath(config_id="cfg-001", file_path=PurePosixPath("/etc/app/config.yaml"))
182+
result = process_config(config=cfg)
183+
184+
# THEN
185+
assert result == {"status": "ok"}

tests/functional/idempotency/_pydantic/test_idempotency_uuid_serialization.py

Lines changed: 0 additions & 157 deletions
This file was deleted.

0 commit comments

Comments
 (0)