fix timezone handling and logging fallback robustness

This commit is contained in:
windyboy
2026-02-09 20:16:39 +08:00
parent 976a1fa1d0
commit aa0dc8ec47
13 changed files with 492 additions and 86 deletions
+7 -1
View File
@@ -7,7 +7,7 @@ from pathlib import Path
import pytest
from click.testing import CliRunner
from vlm.cli import main
from vlm.cli import main, default_config_path
@pytest.fixture
@@ -85,6 +85,12 @@ def test_state_set_and_show(runner, temp_state_file, temp_config):
assert "checked manually" in result.output
def test_default_config_path_resolves_at_runtime(tmp_path, monkeypatch):
"""Test CLI default config path follows current home directory."""
monkeypatch.setattr(Path, 'home', lambda: tmp_path)
assert default_config_path() == tmp_path / ".vlm" / "config.yaml"
def test_state_query(runner, temp_state_file, temp_config):
"""Test querying files by status."""
test_files = [
+2 -2
View File
@@ -10,8 +10,8 @@ from uuid import uuid4
import pytest
from src.vlm.executor import ExecutionEngine
from src.vlm.models import ExecutionPlan, FileOperation
from vlm.executor import ExecutionEngine
from vlm.models import ExecutionPlan, FileOperation
@pytest.fixture
+23
View File
@@ -11,6 +11,7 @@ from vlm.logging_config import (
get_logger,
log_operation,
MAX_LOG_SIZE,
default_log_dir,
)
@@ -71,6 +72,28 @@ class TestLoggingSetup:
logger = setup_logging(log_level=level, log_dir=tmp_path)
assert logger is not None
def test_setup_logging_falls_back_when_file_logging_unwritable(self, tmp_path, monkeypatch):
"""Test setup continues with console logging if file logging cannot initialize."""
def fail_mkdir(self, parents=False, exist_ok=False):
raise PermissionError("mock permission denied")
monkeypatch.setattr(Path, "mkdir", fail_mkdir)
logger = setup_logging(log_level="INFO", log_dir=tmp_path / "logs")
logger.info("Console-only logging still works")
assert len(logger.handlers) == 1
assert isinstance(logger.handlers[0], logging.StreamHandler)
class TestDefaultPathResolution:
"""Test runtime default path resolution."""
def test_default_log_dir_resolves_at_runtime(self, tmp_path, monkeypatch):
"""Test default log directory follows current Path.home() value."""
monkeypatch.setattr(Path, "home", lambda: tmp_path)
assert default_log_dir() == tmp_path / ".vlm" / "logs"
class TestDualOutput:
"""Test dual output to console and file."""
+3 -3
View File
@@ -5,9 +5,9 @@ import pytest
from pathlib import Path
from datetime import datetime
from src.vlm.quarantine import QuarantineManager
from src.vlm.config import Config
from src.vlm.models import QuarantineEntry, QuarantineManifest
from vlm.quarantine import QuarantineManager
from vlm.config import Config
from vlm.models import QuarantineEntry, QuarantineManifest
class TestQuarantineManager:
+37 -3
View File
@@ -5,10 +5,12 @@ Tests inventory reports, completeness reports, duplicate reports, and summary re
import csv
import json
import os
import time
import pytest
from io import StringIO
from pathlib import Path
from datetime import datetime
from datetime import datetime, timezone
from vlm.models import SeasonCompleteness, DuplicateGroup, VideoFile, MovieIdentity, SeriesIdentity
from vlm.reports import (
generate_inventory_report,
@@ -205,12 +207,13 @@ class TestInventoryReport:
def test_timestamp_formatting(self):
"""Test that timestamps are formatted as ISO 8601."""
naive_local = datetime(2023, 6, 15, 14, 30, 45)
files = [
VideoFile(
Path("/test.mkv"),
"test.mkv",
1000,
datetime(2023, 6, 15, 14, 30, 45),
naive_local,
"movie"
)
]
@@ -219,7 +222,38 @@ class TestInventoryReport:
report = generate_inventory_report(files, "csv", library_root)
# Check timestamp format
assert "2023-06-15T14:30:45" in report
expected_utc = naive_local.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
assert expected_utc in report
@pytest.mark.skipif(not hasattr(time, "tzset"), reason="tzset not available on this platform")
def test_naive_timestamp_is_converted_from_local_to_utc(self, monkeypatch):
"""Test report conversion for naive timestamps uses local timezone semantics."""
original_tz = os.environ.get("TZ")
try:
monkeypatch.setenv("TZ", "Etc/GMT-2")
time.tzset()
naive_local = datetime(2023, 6, 15, 14, 30, 45)
expected_utc = naive_local.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
files = [
VideoFile(
Path("/test.mkv"),
"test.mkv",
1000,
naive_local,
"movie"
)
]
report = generate_inventory_report(files, "json", Path("/test"))
data = json.loads(report)
assert data["files"][0]["modified_timestamp"] == expected_utc
finally:
if original_tz is None:
monkeypatch.delenv("TZ", raising=False)
else:
monkeypatch.setenv("TZ", original_tz)
time.tzset()
class TestCompletenessReport:
+37 -1
View File
@@ -2,7 +2,8 @@
import os
import tempfile
from datetime import datetime
import time
from datetime import datetime, timezone
from pathlib import Path
from unittest.mock import patch, MagicMock
import subprocess
@@ -125,6 +126,7 @@ class TestScanLibrary:
assert vf.path == video_file
assert vf.size_bytes > 0
assert isinstance(vf.modified_timestamp, datetime)
assert vf.modified_timestamp.tzinfo == timezone.utc
assert vf.category == "movie"
def test_scan_categorizes_files(self, tmp_path):
@@ -867,3 +869,37 @@ class TestInventoryReports:
assert csv_row['category'] == json_file_data['category']
assert csv_row['resolution'] == json_file_data['resolution']
assert csv_row['codec'] == json_file_data['codec']
@pytest.mark.skipif(not hasattr(time, "tzset"), reason="tzset not available on this platform")
def test_save_inventory_csv_converts_naive_local_time_to_utc(self, tmp_path, monkeypatch):
"""Test naive timestamps are interpreted as local time and converted to UTC."""
original_tz = os.environ.get("TZ")
try:
monkeypatch.setenv("TZ", "Etc/GMT-2")
time.tzset()
naive_local = datetime(2024, 1, 15, 10, 30, 0)
expected_utc = naive_local.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
video_files = [
VideoFile(
path=Path("/library/movie/test.mp4"),
filename="test.mp4",
size_bytes=1024000,
modified_timestamp=naive_local,
category="movie"
)
]
output_file = tmp_path / "inventory.csv"
from vlm.scanner import save_inventory_csv
save_inventory_csv(video_files, output_file, Path("/library"))
content = output_file.read_text(encoding="utf-8")
assert expected_utc in content
finally:
if original_tz is None:
monkeypatch.delenv("TZ", raising=False)
else:
monkeypatch.setenv("TZ", original_tz)
time.tzset()