lllin000_PaperForge/tests/test_pdf_resolver.py
Research Assistant 24e3d9fb30 test(02-01): add failing tests for pdf_resolver path resolution
- 8 test cases covering absolute, vault-relative, junction, storage-relative
- Missing file, has_pdf=False, empty path, whitespace path scenarios
- is_valid_pdf and resolve_junction helper coverage
2026-04-23 12:54:38 +08:00

173 lines
6.2 KiB
Python

"""Tests for paperforge_lite.pdf_resolver.
Covers absolute, vault-relative, junction, storage-relative, missing file,
and has_pdf=False scenarios.
"""
from __future__ import annotations
import os
from pathlib import Path
from unittest.mock import patch
import pytest
from paperforge_lite.pdf_resolver import is_valid_pdf, resolve_junction, resolve_pdf_path
class TestResolvePdfPath:
"""Tests for resolve_pdf_path resolution logic."""
def test_absolute_existing_path(self, tmp_path: Path) -> None:
"""Absolute path to existing PDF returns that path."""
pdf = tmp_path / "test.pdf"
pdf.write_text("PDF content")
result = resolve_pdf_path(str(pdf), True, tmp_path)
assert result == str(pdf.resolve())
def test_vault_relative_existing_path(self, tmp_path: Path) -> None:
"""Vault-relative path resolves against vault root."""
subdir = tmp_path / "storage" / "ABC123"
subdir.mkdir(parents=True)
pdf = subdir / "item.pdf"
pdf.write_text("PDF content")
rel_path = "storage/ABC123/item.pdf"
result = resolve_pdf_path(rel_path, True, tmp_path)
assert result == str(pdf.resolve())
def test_junction_path_mocked(self, tmp_path: Path, monkeypatch) -> None:
"""Junction path returns resolved target path (mocked for cross-platform)."""
target = tmp_path / "target.pdf"
target.write_text("PDF content")
junction = tmp_path / "junction.pdf"
def mock_resolve_junction(path: Path) -> Path:
if path == junction:
return target
return path
monkeypatch.setattr(
"paperforge_lite.pdf_resolver.resolve_junction", mock_resolve_junction
)
result = resolve_pdf_path(str(junction), True, tmp_path)
assert result == str(target.resolve())
def test_zotero_storage_relative_path(self, tmp_path: Path) -> None:
"""storage: prefixed path resolves against zotero_dir."""
zotero_dir = tmp_path / "zotero"
storage_dir = zotero_dir / "ABC123"
storage_dir.mkdir(parents=True)
pdf = storage_dir / "item.pdf"
pdf.write_text("PDF content")
result = resolve_pdf_path("storage:ABC123/item.pdf", True, tmp_path, zotero_dir)
assert result == str(pdf.resolve())
def test_missing_file_returns_empty(self, tmp_path: Path) -> None:
"""Non-existent file returns empty string."""
result = resolve_pdf_path(str(tmp_path / "missing.pdf"), True, tmp_path)
assert result == ""
def test_has_pdf_false_returns_empty(self, tmp_path: Path) -> None:
"""has_pdf=False returns empty string regardless of path."""
pdf = tmp_path / "test.pdf"
pdf.write_text("PDF content")
result = resolve_pdf_path(str(pdf), False, tmp_path)
assert result == ""
def test_empty_path_returns_empty(self, tmp_path: Path) -> None:
"""Empty pdf_path returns empty string."""
result = resolve_pdf_path("", True, tmp_path)
assert result == ""
def test_whitespace_path_returns_empty(self, tmp_path: Path) -> None:
"""Whitespace-only pdf_path returns empty string."""
result = resolve_pdf_path(" ", True, tmp_path)
assert result == ""
class TestIsValidPdf:
"""Tests for is_valid_pdf helper."""
def test_existing_non_empty_file(self, tmp_path: Path) -> None:
"""Existing non-empty file returns True."""
pdf = tmp_path / "test.pdf"
pdf.write_text("PDF content")
assert is_valid_pdf(pdf) is True
def test_missing_file(self, tmp_path: Path) -> None:
"""Missing file returns False."""
assert is_valid_pdf(tmp_path / "missing.pdf") is False
def test_empty_file(self, tmp_path: Path) -> None:
"""Empty file (0 bytes) returns False."""
pdf = tmp_path / "empty.pdf"
pdf.write_text("")
assert is_valid_pdf(pdf) is False
def test_directory_not_file(self, tmp_path: Path) -> None:
"""Directory path returns False."""
subdir = tmp_path / "subdir"
subdir.mkdir()
assert is_valid_pdf(subdir) is False
class TestResolveJunction:
"""Tests for resolve_junction helper."""
def test_nonexistent_path_unchanged(self, tmp_path: Path) -> None:
"""Non-existent path is returned unchanged."""
missing = tmp_path / "missing.pdf"
assert resolve_junction(missing) == missing
def test_regular_file_unchanged(self, tmp_path: Path) -> None:
"""Regular file (not a junction) returns itself."""
pdf = tmp_path / "test.pdf"
pdf.write_text("PDF content")
result = resolve_junction(pdf)
assert result == pdf
def test_symlink_followed(self, tmp_path: Path) -> None:
"""Symbolic link to file is resolved to target."""
target = tmp_path / "target.pdf"
target.write_text("PDF content")
link = tmp_path / "link.pdf"
try:
link.symlink_to(target)
result = resolve_junction(link)
assert result == target
except OSError:
pytest.skip("Symlinks not supported on this platform")
def test_directory_junction_mocked(self, tmp_path: Path, monkeypatch) -> None:
"""Directory junction resolved via Windows API (mocked)."""
target_dir = tmp_path / "target"
target_dir.mkdir()
junction_dir = tmp_path / "junction"
junction_dir.mkdir()
def mock_createfilew(path, *args, **kwargs):
return 42 # valid handle
def mock_getfinalpathname(handle, buf, size, flags):
buf.value = str(target_dir)
return len(str(target_dir))
def mock_closehandle(handle):
pass
monkeypatch.setattr(os, "name", "nt")
monkeypatch.setattr(
"paperforge_lite.pdf_resolver.ctypes.windll.kernel32.CreateFileW",
mock_createfilew,
)
monkeypatch.setattr(
"paperforge_lite.pdf_resolver.ctypes.windll.kernel32.GetFinalPathNameByHandleW",
mock_getfinalpathname,
)
monkeypatch.setattr(
"paperforge_lite.pdf_resolver.ctypes.windll.kernel32.CloseHandle",
mock_closehandle,
)
result = resolve_junction(junction_dir)
assert result == target_dir