Procházet zdrojové kódy

fix(system): emit boot_time and generated_at as tz-aware UTC

  datetime.fromtimestamp(ts) and datetime.now() return naive local
  datetimes; .isoformat() then emits no tz marker. The frontend's
  parseUTCDate helper appends 'Z' to bare strings, treats the value
  as UTC, then converts to local for display — applying the local
  offset twice. Reporter on UTC+3 saw boot_time +3h ahead while
  uptime was correct (uptime is a backend-side delta of two
  naive-local values, so the missing tz info cancels out).

  Fix: pass tz=timezone.utc to datetime.fromtimestamp and
  datetime.now in system.py's boot_time / uptime path, plus the two
  adjacent generated_at sites in system.py and support.py.
maziggy před 2 měsíci
rodič
revize
8c326256db

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
CHANGELOG.md


+ 1 - 1
backend/app/api/routes/support.py

@@ -669,7 +669,7 @@ async def _collect_support_info() -> dict:
     in_docker = is_running_in_docker()
 
     info = {
-        "generated_at": datetime.now().isoformat(),
+        "generated_at": datetime.now(timezone.utc).isoformat(),
         "app": {
             "version": APP_VERSION,
             "debug_mode": settings.debug,

+ 5 - 5
backend/app/api/routes/system.py

@@ -5,7 +5,7 @@ import os
 import platform
 import time
 from collections.abc import Callable
-from datetime import datetime
+from datetime import datetime, timezone
 from pathlib import Path
 
 import psutil
@@ -385,7 +385,7 @@ async def _get_storage_usage_cached(refresh: bool, max_age_seconds: int) -> dict
         snapshot = await asyncio.to_thread(_scan_storage_usage)
         _storage_usage_cache = {
             **snapshot,
-            "generated_at": datetime.now().isoformat(),
+            "generated_at": datetime.now(timezone.utc).isoformat(),
         }
         _storage_usage_cache_ts = time.time()
         return {
@@ -504,10 +504,10 @@ async def get_system_info(
     # (#1690). On bare metal / VMs PID 1 is the host init, which starts at
     # boot, so the value matches psutil.boot_time() within a sub-second.
     try:
-        boot_time = datetime.fromtimestamp(psutil.Process(1).create_time())
+        boot_time = datetime.fromtimestamp(psutil.Process(1).create_time(), tz=timezone.utc)
     except (psutil.Error, OSError):
-        boot_time = datetime.fromtimestamp(psutil.boot_time())
-    uptime_seconds = (datetime.now() - boot_time).total_seconds()
+        boot_time = datetime.fromtimestamp(psutil.boot_time(), tz=timezone.utc)
+    uptime_seconds = (datetime.now(timezone.utc) - boot_time).total_seconds()
 
     # Python and system info
     import sys

+ 33 - 0
backend/tests/integration/test_system_api.py

@@ -336,6 +336,39 @@ class TestSystemAPI:
         result = response.json()
         assert result["system"]["boot_time"].startswith("2023-11-14T")  # 1700000000 UTC
 
+    @pytest.mark.asyncio
+    @pytest.mark.integration
+    async def test_boot_time_isoformat_carries_utc_marker(self, async_client: AsyncClient):
+        """#1690 follow-up: the boot_time string must include a UTC tz marker.
+
+        Without it the frontend's parseUTCDate(...) appends 'Z' to a naive-
+        local-time string, treats it as UTC, and converts to local — applying
+        the local offset twice. The reporter (UTC+3) saw boot_time +3h ahead
+        even though uptime was correct (uptime is computed backend-side from
+        two naive-local values whose delta is right). The fix is to make both
+        ends tz-aware UTC and emit an explicit offset.
+        """
+        with patch("backend.app.api.routes.system.psutil") as mock_psutil:
+            mock_psutil.disk_usage.return_value = MagicMock(
+                total=500000000000, used=250000000000, free=250000000000, percent=50.0
+            )
+            mock_psutil.virtual_memory.return_value = MagicMock(
+                total=16000000000, available=8000000000, used=8000000000, percent=50.0
+            )
+            mock_psutil.boot_time.return_value = 1700000000.0
+            mock_psutil.Process.return_value.create_time.return_value = 1700345600.0
+            mock_psutil.cpu_count.return_value = 4
+            mock_psutil.cpu_percent.return_value = 25.0
+
+            response = await async_client.get("/api/v1/system/info")
+
+        assert response.status_code == 200
+        boot_time = response.json()["system"]["boot_time"]
+        assert boot_time.endswith("+00:00") or boot_time.endswith("Z"), (
+            f"boot_time {boot_time!r} must carry a UTC tz marker; without one the "
+            "frontend double-converts via parseUTCDate"
+        )
+
 
 class TestSystemHelperFunctions:
     """Tests for system info helper functions."""

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů