Add even more tests
This commit is contained in:
@@ -3,3 +3,4 @@
|
|||||||
*.sif
|
*.sif
|
||||||
*.ipynb
|
*.ipynb
|
||||||
backups
|
backups
|
||||||
|
.pytest_cache
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import zipfile
|
import zipfile
|
||||||
import datetime
|
import datetime
|
||||||
|
from unittest.mock import patch
|
||||||
from utils import backup
|
from utils import backup
|
||||||
|
|
||||||
|
|
||||||
@@ -32,3 +33,182 @@ def test_backup_creates_zip(tmp_path, monkeypatch):
|
|||||||
|
|
||||||
# Cleanup: remove the backup file after test
|
# Cleanup: remove the backup file after test
|
||||||
backup_path.unlink()
|
backup_path.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def test_backup_with_subdirectories(tmp_path, monkeypatch):
|
||||||
|
# Setup fake web folder with subdirectories
|
||||||
|
web_folder = tmp_path / 'plots'
|
||||||
|
web_folder.mkdir()
|
||||||
|
(web_folder / 'file1.txt').write_text('content1')
|
||||||
|
|
||||||
|
subdir = web_folder / 'subdir'
|
||||||
|
subdir.mkdir()
|
||||||
|
(subdir / 'file2.txt').write_text('content2')
|
||||||
|
|
||||||
|
nested_subdir = subdir / 'nested'
|
||||||
|
nested_subdir.mkdir()
|
||||||
|
(nested_subdir / 'file3.txt').write_text('content3')
|
||||||
|
|
||||||
|
backup_folder = tmp_path / 'backups'
|
||||||
|
backup_folder.mkdir()
|
||||||
|
|
||||||
|
# Patch the module variables
|
||||||
|
monkeypatch.setattr(backup, 'WEB_FOLDER', web_folder)
|
||||||
|
monkeypatch.setattr(backup, 'BACKUP_FOLDER', backup_folder)
|
||||||
|
|
||||||
|
# Call the backup function
|
||||||
|
backup.create_backup()
|
||||||
|
|
||||||
|
# Check that backup was created with all files
|
||||||
|
today = datetime.date.today().strftime('%Y%m%d')
|
||||||
|
backup_name = f'backup-{today}.zip'
|
||||||
|
backup_path = backup_folder / backup_name
|
||||||
|
|
||||||
|
assert backup_path.exists()
|
||||||
|
with zipfile.ZipFile(backup_path, 'r') as z:
|
||||||
|
names = z.namelist()
|
||||||
|
assert any('file1.txt' in n for n in names)
|
||||||
|
assert any('file2.txt' in n for n in names)
|
||||||
|
assert any('file3.txt' in n for n in names)
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
backup_path.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def test_backup_existing_file(tmp_path, monkeypatch, capsys):
|
||||||
|
# Setup fake web folder
|
||||||
|
web_folder = tmp_path / 'plots'
|
||||||
|
web_folder.mkdir()
|
||||||
|
(web_folder / 'file1.txt').write_text('abc')
|
||||||
|
|
||||||
|
backup_folder = tmp_path / 'backups'
|
||||||
|
backup_folder.mkdir()
|
||||||
|
|
||||||
|
# Create existing backup file
|
||||||
|
today = datetime.date.today().strftime('%Y%m%d')
|
||||||
|
backup_name = f'backup-{today}.zip'
|
||||||
|
backup_path = backup_folder / backup_name
|
||||||
|
backup_path.write_text('existing backup')
|
||||||
|
|
||||||
|
# Patch the module variables
|
||||||
|
monkeypatch.setattr(backup, 'WEB_FOLDER', web_folder)
|
||||||
|
monkeypatch.setattr(backup, 'BACKUP_FOLDER', backup_folder)
|
||||||
|
|
||||||
|
# Call the backup function
|
||||||
|
backup.create_backup()
|
||||||
|
|
||||||
|
# Check that message about existing backup was printed
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert f"Backup already exists: {backup_path}" in captured.out
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
backup_path.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def test_backup_empty_folder(tmp_path, monkeypatch):
|
||||||
|
# Setup empty web folder
|
||||||
|
web_folder = tmp_path / 'plots'
|
||||||
|
web_folder.mkdir()
|
||||||
|
|
||||||
|
backup_folder = tmp_path / 'backups'
|
||||||
|
backup_folder.mkdir()
|
||||||
|
|
||||||
|
# Patch the module variables
|
||||||
|
monkeypatch.setattr(backup, 'WEB_FOLDER', web_folder)
|
||||||
|
monkeypatch.setattr(backup, 'BACKUP_FOLDER', backup_folder)
|
||||||
|
|
||||||
|
# Call the backup function
|
||||||
|
backup.create_backup()
|
||||||
|
|
||||||
|
# Check that backup was created (empty)
|
||||||
|
today = datetime.date.today().strftime('%Y%m%d')
|
||||||
|
backup_name = f'backup-{today}.zip'
|
||||||
|
backup_path = backup_folder / backup_name
|
||||||
|
|
||||||
|
assert backup_path.exists()
|
||||||
|
with zipfile.ZipFile(backup_path, 'r') as z:
|
||||||
|
assert len(z.namelist()) == 0
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
backup_path.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def test_backup_nonexistent_web_folder(tmp_path, monkeypatch):
|
||||||
|
# Setup nonexistent web folder
|
||||||
|
web_folder = tmp_path / 'nonexistent_plots'
|
||||||
|
backup_folder = tmp_path / 'backups'
|
||||||
|
backup_folder.mkdir()
|
||||||
|
|
||||||
|
# Patch the module variables
|
||||||
|
monkeypatch.setattr(backup, 'WEB_FOLDER', web_folder)
|
||||||
|
monkeypatch.setattr(backup, 'BACKUP_FOLDER', backup_folder)
|
||||||
|
|
||||||
|
# Call the backup function
|
||||||
|
backup.create_backup()
|
||||||
|
|
||||||
|
# Check that backup was created (empty since source doesn't exist)
|
||||||
|
today = datetime.date.today().strftime('%Y%m%d')
|
||||||
|
backup_name = f'backup-{today}.zip'
|
||||||
|
backup_path = backup_folder / backup_name
|
||||||
|
|
||||||
|
assert backup_path.exists()
|
||||||
|
with zipfile.ZipFile(backup_path, 'r') as z:
|
||||||
|
assert len(z.namelist()) == 0
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
backup_path.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('datetime.date')
|
||||||
|
def test_backup_with_custom_date(mock_date, tmp_path, monkeypatch):
|
||||||
|
# Mock date to return a specific date
|
||||||
|
mock_date.today.return_value.strftime.return_value = "20230908"
|
||||||
|
|
||||||
|
# Setup fake web folder
|
||||||
|
web_folder = tmp_path / 'plots'
|
||||||
|
web_folder.mkdir()
|
||||||
|
(web_folder / 'file1.txt').write_text('test')
|
||||||
|
|
||||||
|
backup_folder = tmp_path / 'backups'
|
||||||
|
backup_folder.mkdir()
|
||||||
|
|
||||||
|
# Patch the module variables
|
||||||
|
monkeypatch.setattr(backup, 'WEB_FOLDER', web_folder)
|
||||||
|
monkeypatch.setattr(backup, 'BACKUP_FOLDER', backup_folder)
|
||||||
|
|
||||||
|
# Call the backup function
|
||||||
|
backup.create_backup()
|
||||||
|
|
||||||
|
# Check that backup was created with custom date
|
||||||
|
backup_path = backup_folder / "backup-20230908.zip"
|
||||||
|
assert backup_path.exists()
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
backup_path.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def test_backup_folder_creation(tmp_path, monkeypatch):
|
||||||
|
# Setup fake web folder
|
||||||
|
web_folder = tmp_path / 'plots'
|
||||||
|
web_folder.mkdir()
|
||||||
|
(web_folder / 'file1.txt').write_text('test')
|
||||||
|
|
||||||
|
# Don't create backup folder - let function create it
|
||||||
|
backup_folder = tmp_path / 'new_backups'
|
||||||
|
|
||||||
|
# Patch the module variables
|
||||||
|
monkeypatch.setattr(backup, 'WEB_FOLDER', web_folder)
|
||||||
|
monkeypatch.setattr(backup, 'BACKUP_FOLDER', backup_folder)
|
||||||
|
|
||||||
|
# Call the backup function
|
||||||
|
backup.create_backup()
|
||||||
|
|
||||||
|
# Check that backup folder was created
|
||||||
|
assert backup_folder.exists()
|
||||||
|
assert backup_folder.is_dir()
|
||||||
|
|
||||||
|
# Check that backup file was created
|
||||||
|
today = datetime.date.today().strftime('%Y%m%d')
|
||||||
|
backup_name = f'backup-{today}.zip'
|
||||||
|
backup_path = backup_folder / backup_name
|
||||||
|
assert backup_path.exists()
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import pytest
|
||||||
|
import yaml
|
||||||
from utils import config
|
from utils import config
|
||||||
|
|
||||||
|
|
||||||
@@ -25,3 +29,169 @@ def test_metadata_config_defaults():
|
|||||||
mc = config.MetadataConfig()
|
mc = config.MetadataConfig()
|
||||||
assert mc.cache_enabled is True
|
assert mc.cache_enabled is True
|
||||||
assert mc.inherit_from_parent is True
|
assert mc.inherit_from_parent is True
|
||||||
|
assert mc.supported_formats == ['.yaml', '.yml', '.json']
|
||||||
|
|
||||||
|
|
||||||
|
def test_metadata_config_custom():
|
||||||
|
mc = config.MetadataConfig(
|
||||||
|
cache_enabled=False,
|
||||||
|
inherit_from_parent=False,
|
||||||
|
supported_formats=['.yaml']
|
||||||
|
)
|
||||||
|
assert mc.cache_enabled is False
|
||||||
|
assert mc.inherit_from_parent is False
|
||||||
|
assert mc.supported_formats == ['.yaml']
|
||||||
|
|
||||||
|
|
||||||
|
def test_gallery_item():
|
||||||
|
item = config.GalleryItem(name="test", path=Path("/test/path"))
|
||||||
|
assert item.name == "test"
|
||||||
|
assert item.path == Path("/test/path")
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_creation():
|
||||||
|
paths = config.PathConfig(work_dir="/work", web_folder="/web")
|
||||||
|
gallery = config.GalleryConfig(
|
||||||
|
plot_root="plots", png_dpi=300, backup_folder="backups")
|
||||||
|
ui = config.UIConfig(max_recent_plots=5, search_debounce_ms=100)
|
||||||
|
metadata = config.MetadataConfig()
|
||||||
|
|
||||||
|
cfg = config.Config(
|
||||||
|
paths=paths,
|
||||||
|
gallery=gallery,
|
||||||
|
ui=ui,
|
||||||
|
metadata=metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
assert cfg.paths == paths
|
||||||
|
assert cfg.gallery == gallery
|
||||||
|
assert cfg.ui == ui
|
||||||
|
assert cfg.metadata == metadata
|
||||||
|
assert cfg.sources == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_backward_compatibility_properties():
|
||||||
|
paths = config.PathConfig(work_dir="/work", web_folder="/web")
|
||||||
|
gallery = config.GalleryConfig(
|
||||||
|
plot_root="plots", png_dpi=300, backup_folder="backups")
|
||||||
|
ui = config.UIConfig(max_recent_plots=5, search_debounce_ms=100)
|
||||||
|
metadata = config.MetadataConfig()
|
||||||
|
|
||||||
|
cfg = config.Config(
|
||||||
|
paths=paths,
|
||||||
|
gallery=gallery,
|
||||||
|
ui=ui,
|
||||||
|
metadata=metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
assert cfg.web_folder == "/web"
|
||||||
|
assert cfg.png_dpi == 300
|
||||||
|
assert cfg.plot_root == "plots"
|
||||||
|
assert cfg.backup_folder == "backups"
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_from_yaml(tmp_path):
|
||||||
|
yaml_content = {
|
||||||
|
'paths': {
|
||||||
|
'work_dir': '/test/work',
|
||||||
|
'web_folder': '/test/web'
|
||||||
|
},
|
||||||
|
'gallery': {
|
||||||
|
'plot_root': 'test_plots',
|
||||||
|
'png_dpi': 200,
|
||||||
|
'backup_folder': 'test_backups'
|
||||||
|
},
|
||||||
|
'ui': {
|
||||||
|
'max_recent_plots': 15,
|
||||||
|
'search_debounce_ms': 300
|
||||||
|
},
|
||||||
|
'metadata': {
|
||||||
|
'cache_enabled': False,
|
||||||
|
'inherit_from_parent': False
|
||||||
|
},
|
||||||
|
'sources': [
|
||||||
|
{'name': 'source1', 'path': '/path1'},
|
||||||
|
{'name': 'source2', 'path': '/path2'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
yaml_file = tmp_path / 'test_config.yaml'
|
||||||
|
with yaml_file.open('w') as f:
|
||||||
|
yaml.dump(yaml_content, f)
|
||||||
|
|
||||||
|
cfg = config.Config.from_yaml(str(yaml_file))
|
||||||
|
|
||||||
|
assert cfg.paths.work_dir == '/test/work'
|
||||||
|
assert cfg.paths.web_folder == '/test/web'
|
||||||
|
assert cfg.gallery.plot_root == 'test_plots'
|
||||||
|
assert cfg.gallery.png_dpi == 200
|
||||||
|
assert cfg.ui.max_recent_plots == 15
|
||||||
|
assert cfg.metadata.cache_enabled is False
|
||||||
|
assert len(cfg.sources) == 2
|
||||||
|
assert cfg.sources[0].name == 'source1'
|
||||||
|
assert cfg.sources[0].path == Path('/path1')
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_from_yaml_missing_file():
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
config.Config.from_yaml('/nonexistent/file.yaml')
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_from_yaml_malformed():
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml',
|
||||||
|
delete=False) as f:
|
||||||
|
f.write('invalid: yaml: content: [')
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
with pytest.raises(yaml.YAMLError):
|
||||||
|
config.Config.from_yaml(f.name)
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_to_yaml(tmp_path):
|
||||||
|
paths = config.PathConfig(work_dir="/work", web_folder="/web")
|
||||||
|
gallery = config.GalleryConfig(
|
||||||
|
plot_root="plots", png_dpi=300, backup_folder="backups")
|
||||||
|
ui = config.UIConfig(max_recent_plots=5, search_debounce_ms=100)
|
||||||
|
metadata = config.MetadataConfig()
|
||||||
|
sources = [config.GalleryItem(name="test", path=Path("/test"))]
|
||||||
|
|
||||||
|
cfg = config.Config(
|
||||||
|
paths=paths,
|
||||||
|
gallery=gallery,
|
||||||
|
ui=ui,
|
||||||
|
metadata=metadata,
|
||||||
|
sources=sources
|
||||||
|
)
|
||||||
|
|
||||||
|
yaml_file = tmp_path / 'output_config.yaml'
|
||||||
|
cfg.to_yaml(str(yaml_file))
|
||||||
|
|
||||||
|
assert yaml_file.exists()
|
||||||
|
|
||||||
|
# Load back and verify
|
||||||
|
with yaml_file.open('r') as f:
|
||||||
|
loaded_data = yaml.safe_load(f)
|
||||||
|
|
||||||
|
assert loaded_data['paths']['work_dir'] == "/work"
|
||||||
|
assert loaded_data['gallery']['png_dpi'] == 300
|
||||||
|
assert len(loaded_data['sources']) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_from_yaml_partial_data(tmp_path):
|
||||||
|
# Test with minimal YAML data
|
||||||
|
yaml_content = {
|
||||||
|
'paths': {'work_dir': '/min', 'web_folder': '/min_web'},
|
||||||
|
'gallery': {'plot_root': 'min_plots', 'png_dpi': 100,
|
||||||
|
'backup_folder': 'min_backup'},
|
||||||
|
'ui': {'max_recent_plots': 3, 'search_debounce_ms': 50}
|
||||||
|
}
|
||||||
|
|
||||||
|
yaml_file = tmp_path / 'minimal_config.yaml'
|
||||||
|
with yaml_file.open('w') as f:
|
||||||
|
yaml.dump(yaml_content, f)
|
||||||
|
|
||||||
|
cfg = config.Config.from_yaml(str(yaml_file))
|
||||||
|
|
||||||
|
# Should use defaults for metadata and empty sources
|
||||||
|
assert cfg.metadata.cache_enabled is True # default
|
||||||
|
assert cfg.sources == [] # default empty list
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ def test_python_version():
|
|||||||
def test_required_modules():
|
def test_required_modules():
|
||||||
"""Test that required Python modules are installed."""
|
"""Test that required Python modules are installed."""
|
||||||
try:
|
try:
|
||||||
import jinja2 # type: ignore
|
import jinja2 # noqa: F401
|
||||||
import yaml # type: ignore
|
import yaml # noqa: F401
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
pytest.fail(f"Required module not found: {e}")
|
pytest.fail(f"Required module not found: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
from generate_gallery import (
|
||||||
|
convert_pdf_to_png,
|
||||||
|
needs_update,
|
||||||
|
build_gallery,
|
||||||
|
calculate_directory_stats,
|
||||||
|
format_file_size,
|
||||||
|
datetime_from_timestamp,
|
||||||
|
strftime_filter
|
||||||
|
)
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
assert needs_update(source, target) is True
|
||||||
|
|
||||||
|
|
||||||
|
@patch('subprocess.run')
|
||||||
|
def test_convert_pdf_to_png_success(mock_run, tmp_path):
|
||||||
|
pdf_path = tmp_path / "test.pdf"
|
||||||
|
png_path = tmp_path / "test.png"
|
||||||
|
pdf_path.write_text("fake pdf")
|
||||||
|
|
||||||
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
|
|
||||||
|
convert_pdf_to_png(pdf_path)
|
||||||
|
|
||||||
|
mock_run.assert_called_once()
|
||||||
|
call_args = mock_run.call_args[0][0]
|
||||||
|
assert call_args[0] == "convert"
|
||||||
|
assert str(pdf_path) in call_args
|
||||||
|
assert str(png_path) in call_args
|
||||||
|
|
||||||
|
|
||||||
|
@patch('subprocess.run')
|
||||||
|
def test_convert_pdf_to_png_already_exists_newer(mock_run, tmp_path):
|
||||||
|
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
|
||||||
|
import time
|
||||||
|
time.sleep(0.1)
|
||||||
|
png_path.touch()
|
||||||
|
|
||||||
|
convert_pdf_to_png(pdf_path)
|
||||||
|
|
||||||
|
# Should not call subprocess since PNG is newer
|
||||||
|
mock_run.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('subprocess.run')
|
||||||
|
def test_convert_pdf_to_png_pdf_newer(mock_run, tmp_path):
|
||||||
|
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")
|
||||||
|
|
||||||
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
|
|
||||||
|
convert_pdf_to_png(pdf_path)
|
||||||
|
|
||||||
|
# Should call subprocess since PDF is newer
|
||||||
|
mock_run.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('generate_gallery.template')
|
||||||
|
@patch('generate_gallery.save_metadata_cache')
|
||||||
|
@patch('generate_gallery.resolve_metadata_for_plot')
|
||||||
|
@patch('generate_gallery.merge_metadata')
|
||||||
|
@patch('generate_gallery.load_folder_metadata')
|
||||||
|
@patch('generate_gallery.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,
|
||||||
|
mock_template,
|
||||||
|
tmp_path
|
||||||
|
):
|
||||||
|
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")
|
||||||
|
|
||||||
|
# Create corresponding PNG
|
||||||
|
png_file = source_dir / "test.png"
|
||||||
|
png_file.write_text("fake png content")
|
||||||
|
|
||||||
|
# Mock returns
|
||||||
|
mock_load_folder.return_value = {"folder": "metadata"}
|
||||||
|
mock_merge.return_value = {"merged": "metadata"}
|
||||||
|
mock_resolve.return_value = {"plot": "metadata"}
|
||||||
|
mock_template.render.return_value = "<html>test</html>"
|
||||||
|
|
||||||
|
build_gallery(source_dir, web_dir)
|
||||||
|
|
||||||
|
# Verify mocks were called
|
||||||
|
mock_load_folder.assert_called_once_with(source_dir)
|
||||||
|
mock_convert.assert_called_once_with(pdf_file)
|
||||||
|
mock_copy.assert_called() # Should be called for PDF and PNG
|
||||||
|
mock_save_cache.assert_called_once()
|
||||||
|
|
||||||
|
# Check HTML file was created
|
||||||
|
html_file = web_dir / "index.html"
|
||||||
|
assert html_file.exists()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('generate_gallery.template')
|
||||||
|
@patch('generate_gallery.save_metadata_cache')
|
||||||
|
@patch('generate_gallery.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 = "<html>test</html>"
|
||||||
|
|
||||||
|
build_gallery(source_dir, web_dir)
|
||||||
|
|
||||||
|
# Check subdirectory was created in web
|
||||||
|
web_subdir = web_dir / "subdir"
|
||||||
|
assert web_subdir.exists()
|
||||||
|
assert web_subdir.is_dir()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('generate_gallery.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
|
||||||
|
|
||||||
|
with patch('generate_gallery.template') as mock_template:
|
||||||
|
mock_template.render.return_value = "<html>test</html>"
|
||||||
|
build_gallery(source_dir, web_dir)
|
||||||
|
|
||||||
|
# copy2 should not be called since files are up to date
|
||||||
|
mock_copy.assert_not_called()
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import pytest
|
||||||
from utils import metadata
|
from utils import metadata
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@@ -23,3 +24,179 @@ def test_load_metadata_file_missing(tmp_path):
|
|||||||
missing_path = tmp_path / 'nope.yaml'
|
missing_path = tmp_path / 'nope.yaml'
|
||||||
result = metadata.load_metadata_file(missing_path)
|
result = metadata.load_metadata_file(missing_path)
|
||||||
assert result == {}
|
assert result == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_metadata_file_yml_extension(tmp_path):
|
||||||
|
data = {'test': 'yml_format'}
|
||||||
|
yml_path = tmp_path / 'meta.yml'
|
||||||
|
yml_path.write_text(yaml.dump(data))
|
||||||
|
result = metadata.load_metadata_file(yml_path)
|
||||||
|
assert result == data
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_metadata_file_unknown_format(tmp_path):
|
||||||
|
txt_path = tmp_path / 'meta.txt'
|
||||||
|
txt_path.write_text('some text')
|
||||||
|
result = metadata.load_metadata_file(txt_path)
|
||||||
|
assert result == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_metadata_file_malformed_yaml(tmp_path):
|
||||||
|
yaml_path = tmp_path / 'bad.yaml'
|
||||||
|
yaml_path.write_text('invalid: yaml: content: [')
|
||||||
|
with pytest.raises(yaml.YAMLError):
|
||||||
|
metadata.load_metadata_file(yaml_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_metadata_file_malformed_json(tmp_path):
|
||||||
|
json_path = tmp_path / 'bad.json'
|
||||||
|
json_path.write_text('{"invalid": json}')
|
||||||
|
with pytest.raises(json.JSONDecodeError):
|
||||||
|
metadata.load_metadata_file(json_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_folder_metadata_yaml(tmp_path):
|
||||||
|
data = {'folder': 'metadata'}
|
||||||
|
metadata_path = tmp_path / 'metadata.yaml'
|
||||||
|
metadata_path.write_text(yaml.dump(data))
|
||||||
|
result = metadata.load_folder_metadata(tmp_path)
|
||||||
|
assert result == data
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_folder_metadata_yml(tmp_path):
|
||||||
|
data = {'folder': 'metadata_yml'}
|
||||||
|
metadata_path = tmp_path / 'metadata.yml'
|
||||||
|
metadata_path.write_text(yaml.dump(data))
|
||||||
|
result = metadata.load_folder_metadata(tmp_path)
|
||||||
|
assert result == data
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_folder_metadata_json(tmp_path):
|
||||||
|
data = {'folder': 'metadata_json'}
|
||||||
|
metadata_path = tmp_path / 'metadata.json'
|
||||||
|
metadata_path.write_text(json.dumps(data))
|
||||||
|
result = metadata.load_folder_metadata(tmp_path)
|
||||||
|
assert result == data
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_folder_metadata_missing(tmp_path):
|
||||||
|
result = metadata.load_folder_metadata(tmp_path)
|
||||||
|
assert result == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_metadata_file_path_existing_yaml(tmp_path):
|
||||||
|
metadata_path = tmp_path / 'metadata.yaml'
|
||||||
|
metadata_path.write_text('test: data')
|
||||||
|
result = metadata.get_metadata_file_path(tmp_path)
|
||||||
|
assert result == str(metadata_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_metadata_file_path_existing_yml(tmp_path):
|
||||||
|
metadata_path = tmp_path / 'metadata.yml'
|
||||||
|
metadata_path.write_text('test: data')
|
||||||
|
result = metadata.get_metadata_file_path(tmp_path)
|
||||||
|
assert result == str(metadata_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_metadata_file_path_existing_json(tmp_path):
|
||||||
|
metadata_path = tmp_path / 'metadata.json'
|
||||||
|
metadata_path.write_text('{"test": "data"}')
|
||||||
|
result = metadata.get_metadata_file_path(tmp_path)
|
||||||
|
assert result == str(metadata_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_metadata_file_path_none_existing(tmp_path):
|
||||||
|
result = metadata.get_metadata_file_path(tmp_path)
|
||||||
|
expected = str(tmp_path / 'metadata.yaml')
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_merge_metadata():
|
||||||
|
parent = {'project': 'Test', 'version': '1.0', 'author': 'Parent'}
|
||||||
|
child = {'experiment': 'A', 'version': '1.1'}
|
||||||
|
merged = metadata.merge_metadata(parent, child)
|
||||||
|
expected = {'project': 'Test', 'version': '1.1',
|
||||||
|
'author': 'Parent', 'experiment': 'A'}
|
||||||
|
assert merged == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_merge_metadata_empty_parent():
|
||||||
|
parent = {}
|
||||||
|
child = {'experiment': 'A', 'version': '1.1'}
|
||||||
|
merged = metadata.merge_metadata(parent, child)
|
||||||
|
assert merged == child
|
||||||
|
|
||||||
|
|
||||||
|
def test_merge_metadata_empty_child():
|
||||||
|
parent = {'project': 'Test', 'version': '1.0'}
|
||||||
|
child = {}
|
||||||
|
merged = metadata.merge_metadata(parent, child)
|
||||||
|
assert merged == parent
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_metadata_for_plot_with_specific_yaml(tmp_path):
|
||||||
|
# Create plot-specific metadata file
|
||||||
|
plot_path = tmp_path / 'test_plot.pdf'
|
||||||
|
plot_metadata_path = tmp_path / 'test_plot.yaml'
|
||||||
|
plot_metadata = {'specific': 'plot_data', 'override': 'plot_value'}
|
||||||
|
plot_metadata_path.write_text(yaml.dump(plot_metadata))
|
||||||
|
|
||||||
|
inherited = {'general': 'data', 'override': 'inherited_value'}
|
||||||
|
result = metadata.resolve_metadata_for_plot(plot_path, inherited)
|
||||||
|
|
||||||
|
expected = {'general': 'data',
|
||||||
|
'override': 'plot_value', 'specific': 'plot_data'}
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_metadata_for_plot_with_specific_json(tmp_path):
|
||||||
|
plot_path = tmp_path / 'test_plot.pdf'
|
||||||
|
plot_metadata_path = tmp_path / 'test_plot.json'
|
||||||
|
plot_metadata = {'specific': 'plot_data_json'}
|
||||||
|
plot_metadata_path.write_text(json.dumps(plot_metadata))
|
||||||
|
|
||||||
|
inherited = {'general': 'data'}
|
||||||
|
result = metadata.resolve_metadata_for_plot(plot_path, inherited)
|
||||||
|
|
||||||
|
expected = {'general': 'data', 'specific': 'plot_data_json'}
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_metadata_for_plot_no_specific(tmp_path):
|
||||||
|
plot_path = tmp_path / 'test_plot.pdf'
|
||||||
|
inherited = {'general': 'data', 'inherited': 'value'}
|
||||||
|
result = metadata.resolve_metadata_for_plot(plot_path, inherited)
|
||||||
|
|
||||||
|
# Should return copy of inherited metadata
|
||||||
|
assert result == inherited
|
||||||
|
assert result is not inherited # Should be a copy
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_metadata_cache(tmp_path):
|
||||||
|
cache_data = {
|
||||||
|
'plot1': {'title': 'Plot 1', 'author': 'Test'},
|
||||||
|
'plot2': {'title': 'Plot 2', 'experiment': 'B'}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.save_metadata_cache(tmp_path, cache_data)
|
||||||
|
|
||||||
|
cache_file = tmp_path / 'meta_cache.json'
|
||||||
|
assert cache_file.exists()
|
||||||
|
|
||||||
|
with cache_file.open('r') as f:
|
||||||
|
loaded_data = json.load(f)
|
||||||
|
|
||||||
|
assert loaded_data == cache_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_metadata_cache_empty(tmp_path):
|
||||||
|
cache_data = {}
|
||||||
|
metadata.save_metadata_cache(tmp_path, cache_data)
|
||||||
|
|
||||||
|
cache_file = tmp_path / 'meta_cache.json'
|
||||||
|
assert cache_file.exists()
|
||||||
|
|
||||||
|
with cache_file.open('r') as f:
|
||||||
|
loaded_data = json.load(f)
|
||||||
|
|
||||||
|
assert loaded_data == {}
|
||||||
|
|||||||
Reference in New Issue
Block a user