From 25056602d89f13d2517b3e9ce6d74424c419b44a Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Wed, 27 May 2026 14:55:05 +0300 Subject: [PATCH] fix: serialize webapp datetime payloads --- backend/bot/app/web/webapp/billing.py | 12 ++++-------- backend/bot/app/web/webapp/devices.py | 11 ++++++++++- tests/test_webapp_devices_serialization.py | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 tests/test_webapp_devices_serialization.py diff --git a/backend/bot/app/web/webapp/billing.py b/backend/bot/app/web/webapp/billing.py index 6c1522a..1662d5b 100644 --- a/backend/bot/app/web/webapp/billing.py +++ b/backend/bot/app/web/webapp/billing.py @@ -688,15 +688,11 @@ async def device_topup_options_route(request: web.Request) -> web.Response: "currency": settings.DEFAULT_CURRENCY_SYMBOL or "RUB", "title": f"+{count}", "subtitle": tariff.name(lang), - "valid_from": ( - (rub_quote or stars_quote)["valid_from"].isoformat() - if (rub_quote or stars_quote).get("valid_from") - else None + "valid_from": _billing_iso_datetime( + (rub_quote or stars_quote).get("valid_from") ), - "valid_until": ( - (rub_quote or stars_quote)["valid_until"].isoformat() - if (rub_quote or stars_quote).get("valid_until") - else None + "valid_until": _billing_iso_datetime( + (rub_quote or stars_quote).get("valid_until") ), "proration_ratio": float((rub_quote or stars_quote).get("proration_ratio") or 0), } diff --git a/backend/bot/app/web/webapp/devices.py b/backend/bot/app/web/webapp/devices.py index a90eaa0..2aefe9f 100644 --- a/backend/bot/app/web/webapp/devices.py +++ b/backend/bot/app/web/webapp/devices.py @@ -193,6 +193,15 @@ def _format_device_datetime(value: Any) -> str: return text +def _serialize_device_datetime(value: Any) -> Optional[str]: + if not value: + return None + if isinstance(value, datetime): + normalized = value if value.tzinfo else value.replace(tzinfo=timezone.utc) + return normalized.isoformat() + return str(value) + + def _serialize_device(device: Dict[str, Any], index: int) -> Dict[str, Any]: hwid = str(device.get("hwid") or "").strip() model = str(device.get("deviceModel") or "").strip() @@ -208,7 +217,7 @@ def _serialize_device(device: Dict[str, Any], index: int) -> Dict[str, Any]: "os_version": os_version, "platform_label": platform_label, "user_agent": user_agent, - "created_at": device.get("createdAt"), + "created_at": _serialize_device_datetime(device.get("createdAt")), "created_at_text": _format_device_datetime(device.get("createdAt")), "hwid_short": _shorten_hwid_for_display(hwid), "token": _device_hwid_token(hwid) if hwid else "", diff --git a/tests/test_webapp_devices_serialization.py b/tests/test_webapp_devices_serialization.py new file mode 100644 index 0000000..591b49e --- /dev/null +++ b/tests/test_webapp_devices_serialization.py @@ -0,0 +1,22 @@ +import json +from datetime import datetime, timezone + +import bot.app.web.subscription_webapp # noqa: F401 +from bot.app.web.webapp.devices import _serialize_device + + +def test_device_serializer_accepts_datetime_created_at(): + created_at = datetime(2099, 1, 2, 3, 4, tzinfo=timezone.utc) + + payload = _serialize_device( + { + "hwid": "abcdef123456", + "deviceModel": "Laptop", + "createdAt": created_at, + }, + 1, + ) + + assert payload["created_at"] == created_at.isoformat() + assert payload["created_at_text"] == "02.01.2099 03:04" + json.dumps(payload)