fix timezone handling and logging fallback robustness
This commit is contained in:
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user