Files
2026-03-26 18:04:54 +09:00

360 lines
14 KiB
Python

## test_main.py
```python
"""
Comprehensive test suite for pdf_opener/main.py.
Tests cover PDFOpener class methods, parse_args function,
edge cases, error handling, and the main entry point.
"""
import argparse
import sys
import unittest
from io import StringIO
from typing import Optional
from unittest.mock import MagicMock, patch, PropertyMock
# Adjust import path since tests run from /data
sys.path.insert(0, "/data")
from pdf_opener.main import PDFOpener, parse_args, main # noqa: E402
## Helper Factories
def make_mock_page(text: str = "Sample page text") -> MagicMock:
"""Create a mock PDF page with configurable extract_text return value."""
page = MagicMock()
page.extract_text.return_value = text
return page
def make_mock_metadata(
title: Optional[str] = "Test Title",
author: Optional[str] = "Test Author",
subject: Optional[str] = "Test Subject",
creator: Optional[str] = "Test Creator",
producer: Optional[str] = "Test Producer",
creation_date: Optional[str] = "2024-01-01",
) -> MagicMock:
"""Create a mock PDF metadata object."""
metadata = MagicMock()
metadata.title = title
metadata.author = author
metadata.subject = subject
metadata.creator = creator
metadata.producer = producer
metadata.creation_date = creation_date
return metadata
def make_mock_reader(
num_pages: int = 3,
page_texts: Optional[list] = None,
metadata: Optional[MagicMock] = None,
) -> MagicMock:
"""Create a mock PdfReader with configurable pages and metadata."""
reader = MagicMock()
if page_texts is None:
page_texts = [f"Page {i + 1} content" for i in range(num_pages)]
pages = [make_mock_page(text) for text in page_texts]
reader.pages = pages
reader.metadata = metadata if metadata is not None else make_mock_metadata()
return reader
## TestPDFOpenerInit
class TestPDFOpenerInit(unittest.TestCase):
"""Tests for PDFOpener.__init__ method."""
def test_default_initialization(self) -> None:
"""Test PDFOpener initializes with correct default values."""
opener = PDFOpener(file_path="test.pdf")
self.assertEqual(opener._file_path, "test.pdf")
self.assertEqual(opener._start_page, 0)
self.assertIsNone(opener._end_page)
def test_custom_start_page(self) -> None:
"""Test PDFOpener initializes with custom start_page."""
opener = PDFOpener(file_path="test.pdf", start_page=2)
self.assertEqual(opener._start_page, 2)
def test_custom_end_page(self) -> None:
"""Test PDFOpener initializes with custom end_page."""
opener = PDFOpener(file_path="test.pdf", end_page=5)
self.assertEqual(opener._end_page, 5)
def test_full_custom_initialization(self) -> None:
"""Test PDFOpener initializes with all custom values."""
opener = PDFOpener(file_path="/path/to/doc.pdf", start_page=1, end_page=4)
self.assertEqual(opener._file_path, "/path/to/doc.pdf")
self.assertEqual(opener._start_page, 1)
self.assertEqual(opener._end_page, 4)
def test_file_path_type_is_str(self) -> None:
"""Test that file_path is stored as string."""
opener = PDFOpener(file_path="sample.pdf")
self.assertIsInstance(opener._file_path, str)
def test_start_page_type_is_int(self) -> None:
"""Test that start_page is stored as int."""
opener = PDFOpener(file_path="sample.pdf", start_page=3)
self.assertIsInstance(opener._start_page, int)
def test_end_page_none_by_default(self) -> None:
"""Test that end_page defaults to None."""
opener = PDFOpener(file_path="sample.pdf")
self.assertIsNone(opener._end_page)
## TestPDFOpenerOpen
class TestPDFOpenerOpen(unittest.TestCase):
"""Tests for PDFOpener.open method."""
@patch("pdf_opener.main.PdfReader")
def test_open_returns_reader_on_success(self, mock_pdf_reader: MagicMock) -> None:
"""Test that open() returns a PdfReader instance on success."""
mock_reader_instance = MagicMock()
mock_pdf_reader.return_value = mock_reader_instance
opener = PDFOpener(file_path="valid.pdf")
result = opener.open()
mock_pdf_reader.assert_called_once_with("valid.pdf")
self.assertEqual(result, mock_reader_instance)
@patch("pdf_opener.main.PdfReader")
def test_open_file_not_found_exits(self, mock_pdf_reader: MagicMock) -> None:
"""Test that open() calls sys.exit(1) when file is not found."""
mock_pdf_reader.side_effect = FileNotFoundError
opener = PDFOpener(file_path="nonexistent.pdf")
with self.assertRaises(SystemExit) as context:
opener.open()
self.assertEqual(context.exception.code, 1)
@patch("pdf_opener.main.PdfReader")
def test_open_file_not_found_prints_error(self, mock_pdf_reader: MagicMock) -> None:
"""Test that open() prints error message to stderr on FileNotFoundError."""
mock_pdf_reader.side_effect = FileNotFoundError
opener = PDFOpener(file_path="nonexistent.pdf")
with self.assertRaises(SystemExit):
with patch("sys.stderr", new_callable=StringIO) as mock_stderr:
opener.open()
self.assertIn("nonexistent.pdf", mock_stderr.getvalue())
@patch("pdf_opener.main.PdfReader")
def test_open_pdf_read_error_exits(self, mock_pdf_reader: MagicMock) -> None:
"""Test that open() calls sys.exit(1) on PdfReadError."""
import pypdf
mock_pdf_reader.side_effect = pypdf.errors.PdfReadError("corrupted")
opener = PDFOpener(file_path="corrupted.pdf")
with self.assertRaises(SystemExit) as context:
opener.open()
self.assertEqual(context.exception.code, 1)
@patch("pdf_opener.main.PdfReader")
def test_open_pdf_read_error_prints_error(self, mock_pdf_reader: MagicMock) -> None:
"""Test that open() prints error message to stderr on PdfReadError."""
import pypdf
mock_pdf_reader.side_effect = pypdf.errors.PdfReadError("corrupted")
opener = PDFOpener(file_path="corrupted.pdf")
with self.assertRaises(SystemExit):
with patch("sys.stderr", new_callable=StringIO) as mock_stderr:
opener.open()
self.assertIn("corrupted", mock_stderr.getvalue())
@patch("pdf_opener.main.PdfReader")
def test_open_permission_error_exits(self, mock_pdf_reader: MagicMock) -> None:
"""Test that open() calls sys.exit(1) on PermissionError."""
mock_pdf_reader.side_effect = PermissionError
opener = PDFOpener(file_path="protected.pdf")
with self.assertRaises(SystemExit) as context:
opener.open()
self.assertEqual(context.exception.code, 1)
@patch("pdf_opener.main.PdfReader")
def test_open_permission_error_prints_error(self, mock_pdf_reader: MagicMock) -> None:
"""Test that open() prints error message to stderr on PermissionError."""
mock_pdf_reader.side_effect = PermissionError
opener = PDFOpener(file_path="protected.pdf")
with self.assertRaises(SystemExit):
with patch("sys.stderr", new_callable=StringIO) as mock_stderr:
opener.open()
self.assertIn("protected.pdf", mock_stderr.getvalue())
## TestPDFOpenerGetInfo
class TestPDFOpenerGetInfo(unittest.TestCase):
"""Tests for PDFOpener.get_info method."""
def setUp(self) -> None:
"""Set up common test fixtures."""
self.opener = PDFOpener(file_path="test.pdf")
def test_get_info_returns_dict(self) -> None:
"""Test that get_info returns a dictionary."""
reader = make_mock_reader(num_pages=5)
result = self.opener.get_info(reader)
self.assertIsInstance(result, dict)
def test_get_info_num_pages(self) -> None:
"""Test that get_info correctly reports number of pages."""
reader = make_mock_reader(num_pages=7)
result = self.opener.get_info(reader)
self.assertEqual(result["num_pages"], 7)
def test_get_info_with_full_metadata(self) -> None:
"""Test get_info with complete metadata."""
metadata = make_mock_metadata(
title="My PDF",
author="John Doe",
subject="Testing",
creator="Word",
producer="Adobe",
creation_date="2023-06-15",
)
reader = make_mock_reader(num_pages=3, metadata=metadata)
result = self.opener.get_info(reader)
self.assertEqual(result["title"], "My PDF")
self.assertEqual(result["author"], "John Doe")
self.assertEqual(result["subject"], "Testing")
self.assertEqual(result["creator"], "Word")
self.assertEqual(result["producer"], "Adobe")
self.assertEqual(result["creation_date"], "2023-06-15")
def test_get_info_with_none_metadata(self) -> None:
"""Test get_info when metadata is None."""
reader = make_mock_reader(num_pages=2, metadata=None)
result = self.opener.get_info(reader)
self.assertEqual(result["title"], "未知")
self.assertEqual(result["author"], "未知")
self.assertEqual(result["subject"], "未知")
self.assertEqual(result["creator"], "未知")
self.assertEqual(result["producer"], "未知")
self.assertEqual(result["creation_date"], "未知")
def test_get_info_with_partial_metadata_none_fields(self) -> None:
"""Test get_info when some metadata fields are None."""
metadata = make_mock_metadata(
title=None,
author="Known Author",
subject=None,
creator=None,
producer="Known Producer",
creation_date=None,
)
reader = make_mock_reader(num_pages=1, metadata=metadata)
result = self.opener.get_info(reader)
self.assertEqual(result["title"], "未知")
self.assertEqual(result["author"], "Known Author")
self.assertEqual(result["subject"], "未知")
self.assertEqual(result["creator"], "未知")
self.assertEqual(result["producer"], "Known Producer")
self.assertEqual(result["creation_date"], "未知")
def test_get_info_contains_all_keys(self) -> None:
"""Test that get_info result contains all expected keys."""
reader = make_mock_reader(num_pages=1)
result = self.opener.get_info(reader)
expected_keys = [
"num_pages", "title", "author", "subject",
"creator", "producer", "creation_date"
]
for key in expected_keys:
self.assertIn(key, result)
def test_get_info_single_page_pdf(self) -> None:
"""Test get_info with a single page PDF."""
reader = make_mock_reader(num_pages=1)
result = self.opener.get_info(reader)
self.assertEqual(result["num_pages"], 1)
## TestPDFOpenerDisplayInfo
class TestPDFOpenerDisplayInfo(unittest.TestCase):
"""Tests for PDFOpener.display_info method."""
def setUp(self) -> None:
"""Set up common test fixtures."""
self.opener = PDFOpener(file_path="test.pdf")
self.sample_info: dict = {
"num_pages": 10,
"title": "Sample Title",
"author": "Sample Author",
"subject": "Sample Subject",
"creator": "Sample Creator",
"producer": "Sample Producer",
"creation_date": "2024-01-01",
}
def test_display_info_prints_num_pages(self) -> None:
"""Test that display_info prints the number of pages."""
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
self.opener.display_info(self.sample_info)
output = mock_stdout.getvalue()
self.assertIn("10", output)
def test_display_info_prints_title(self) -> None:
"""Test that display_info prints the title."""
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
self.opener.display_info(self.sample_info)
output = mock_stdout.getvalue()
self.assertIn("Sample Title", output)
def test_display_info_prints_author(self) -> None:
"""Test that display_info prints the author."""
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
self.opener.display_info(self.sample_info)
output = mock_stdout.getvalue()
self.assertIn("Sample Author", output)
def test_display_info_prints_separator(self) -> None:
"""Test that display_info prints separator lines."""
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
self.opener.display_info(self.sample_info)
output = mock_stdout.getvalue()
self.assertIn("=" * 50, output)
def test_display_info_prints_header(self) -> None:
"""Test that display_info prints the header text."""
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
self.opener.display_info(self.sample_info)
output = mock_stdout.getvalue()
self.assertIn("PDF 文件基本信息", output)
def test_display_info_with_unknown_values(self) -> None:
"""Test display_info with unknown/missing values."""
info: dict = {
"num_pages": 1,
"title": "未知",
"author": "未知",
"subject": "未知",
"creator": "未知",
"producer": "未知",
"creation_date": "未知",
}
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
self.opener.display_info(info)
output = mock_stdout.getvalue()
self.assertIn("未知", output)
def test_display_info_with_empty_dict(self) -> None:
"""Test display_info with empty dict uses default '未知'."""
with patch("sys.stdout", new_callable=StringIO) as