import os from datetime import datetime from pathlib import Path from unittest.mock import patch from gallery import ( build_gallery, calculate_directory_stats, convert_pdf_to_png, datetime_from_timestamp, format_file_size, needs_update, strftime_filter, ) def test_format_file_size(): assert format_file_size(0) == "0 B" assert format_file_size(1) == "1.0 B" assert format_file_size(1024) == "1.0 KB" assert format_file_size(1048576) == "1.0 MB" assert format_file_size(1073741824) == "1.0 GB" assert format_file_size(1099511627776) == "1.0 TB" def test_format_file_size_edge_cases(): assert format_file_size(1023) == "1023.0 B" assert format_file_size(1536) == "1.5 KB" assert format_file_size(2621440) == "2.5 MB" def test_needs_update_missing_target(tmp_path): source = tmp_path / "source.txt" target = tmp_path / "target.txt" source.write_text("test content") assert needs_update(source, target) is True def test_needs_update_target_newer(tmp_path): source = tmp_path / "source.txt" target = tmp_path / "target.txt" source.write_text("test") target.write_text("test") # Make target newer by modifying its timestamp import time time.sleep(0.1) target.touch() assert needs_update(source, target) is False def test_needs_update_source_newer(tmp_path): source = tmp_path / "source.txt" target = tmp_path / "target.txt" target.write_text("test") import time time.sleep(0.1) source.write_text("test") # Force different modification times with 31+ second buffer target_time = target.stat().st_mtime source_time = target_time + 40 # 40 seconds newer (> 30 second buffer) os.utime(source, (source_time, source_time)) assert needs_update(source, target) is True @patch("gallery.utils.processing._convert_pdf_pymupdf") def test_convert_pdf_to_png_success(mock_convert, tmp_path): from gallery import GalleryConfig pdf_path = tmp_path / "test.pdf" png_path = tmp_path / "test.png" pdf_path.write_text("fake pdf") config = GalleryConfig(tmp_path) convert_pdf_to_png(pdf_path, config) mock_convert.assert_called_once_with(pdf_path, png_path, config.png_dpi) @patch("gallery.utils.processing._convert_pdf_pymupdf") def test_convert_pdf_to_png_already_exists_newer(mock_convert, tmp_path): from gallery import GalleryConfig pdf_path = tmp_path / "test.pdf" png_path = tmp_path / "test.png" pdf_path.write_text("fake pdf") png_path.write_text("fake png") # Make PNG much newer than PDF using explicit time pdf_time = pdf_path.stat().st_mtime png_time = pdf_time + 100 # PNG is 100 seconds newer os.utime(png_path, (png_time, png_time)) convert_pdf_to_png(pdf_path, GalleryConfig(tmp_path)) # Should not convert since PNG is newer mock_convert.assert_not_called() @patch("gallery.utils.processing._convert_pdf_pymupdf") def test_convert_pdf_to_png_pdf_newer(mock_convert, tmp_path): from gallery import GalleryConfig pdf_path = tmp_path / "test.pdf" png_path = tmp_path / "test.png" png_path.write_text("fake png") import time time.sleep(0.1) pdf_path.write_text("fake pdf") convert_pdf_to_png(pdf_path, GalleryConfig(tmp_path)) # Should convert since PDF is newer mock_convert.assert_called_once() def test_calculate_directory_stats_empty(tmp_path): stats = calculate_directory_stats(tmp_path) assert stats["file_count"] == 0 assert stats["folder_count"] == 0 assert stats["total_size"] == 0 assert stats["pdf_size"] == 0 assert stats["png_size"] == 0 def test_calculate_directory_stats_with_files(tmp_path): # Create test files (tmp_path / "test.pdf").write_text("pdf content") (tmp_path / "test.png").write_text("png content") (tmp_path / "test.txt").write_text("txt content") # Create subdirectory subdir = tmp_path / "subdir" subdir.mkdir() (subdir / "nested.pdf").write_text("nested pdf") stats = calculate_directory_stats(tmp_path) assert stats["file_count"] == 4 assert stats["folder_count"] == 1 assert stats["total_size"] > 0 assert stats["pdf_size"] > 0 assert stats["png_size"] > 0 def test_calculate_directory_stats_nonexistent(): nonexistent = Path("/nonexistent/path") stats = calculate_directory_stats(nonexistent) assert stats["file_count"] == 0 assert stats["folder_count"] == 0 assert stats["total_size"] == 0 def test_datetime_from_timestamp(): timestamp = 1630000000 # Some Unix timestamp dt = datetime_from_timestamp(timestamp) assert isinstance(dt, datetime) assert dt.timestamp() == timestamp def test_strftime_filter(): dt = datetime(2023, 9, 8, 14, 30, 0) formatted = strftime_filter(dt, "%Y-%m-%d %H:%M") assert formatted == "2023-09-08 14:30" @patch("gallery.builder.save_metadata_cache") @patch("gallery.utils.processing.resolve_metadata_for_plot") @patch("gallery.builder.merge_metadata") @patch("gallery.builder.load_folder_metadata") @patch("gallery.utils.processing.convert_pdf_to_png") @patch("shutil.copy2") def test_build_gallery_basic( mock_copy, mock_convert, mock_load_folder, mock_merge, mock_resolve, mock_save_cache, tmp_path ): from gallery import GalleryConfig, get_template source_dir = tmp_path / "source" web_dir = tmp_path / "web" source_dir.mkdir() web_dir.mkdir() # Create a test PDF pdf_file = source_dir / "test.pdf" pdf_file.write_text("fake pdf content") # Don't create PNG - this will trigger convert_pdf_to_png call # Mock returns mock_load_folder.return_value = {"folder": "metadata"} mock_merge.return_value = {"merged": "metadata"} mock_resolve.return_value = {"plot": "metadata"} config = GalleryConfig(tmp_path) build_gallery(config, source_dir, web_dir, template=get_template()) # Verify mocks were called mock_convert.assert_called_once_with(pdf_file, config=config) mock_copy.assert_called() # Should be called for PDF mock_save_cache.assert_called_once() # Check HTML file was created by the real render_gallery_page html_file = web_dir / "index.html" assert html_file.exists() @patch("gallery.builder.render_gallery_page") @patch("gallery.builder.save_metadata_cache") @patch("gallery.builder.load_folder_metadata") def test_build_gallery_with_subdirs(mock_load_folder, mock_save_cache, mock_template, tmp_path): source_dir = tmp_path / "source" web_dir = tmp_path / "web" source_dir.mkdir() web_dir.mkdir() # Create subdirectory subdir = source_dir / "subdir" subdir.mkdir() mock_load_folder.return_value = {} mock_template.render.return_value = "test" from gallery import GalleryConfig build_gallery(config=GalleryConfig(tmp_path), source_dir=source_dir, web_dir=web_dir) # Check subdirectory was created in web web_subdir = web_dir / "subdir" assert web_subdir.exists() assert web_subdir.is_dir() @patch("gallery.utils.processing.needs_update") @patch("shutil.copy2") def test_build_gallery_skip_up_to_date(mock_copy, mock_needs_update, tmp_path): source_dir = tmp_path / "source" web_dir = tmp_path / "web" source_dir.mkdir() web_dir.mkdir() # Create test files pdf_file = source_dir / "test.pdf" png_file = source_dir / "test.png" pdf_file.write_text("pdf") png_file.write_text("png") # Create target files web_pdf = web_dir / "test.pdf" web_png = web_dir / "test.png" web_pdf.write_text("pdf") web_png.write_text("png") # Mock needs_update to return False (up to date) mock_needs_update.return_value = False from gallery import get_template get_template() from gallery import GalleryConfig build_gallery(GalleryConfig(tmp_path), source_dir, web_dir) # copy2 should not be called since files are up to date mock_copy.assert_not_called()