199 lines
6.4 KiB
Python
199 lines
6.4 KiB
Python
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
from typer.testing import CliRunner
|
|
|
|
from photools3.cli import app
|
|
from photools3.processing import ProcessingMode
|
|
|
|
runner = CliRunner()
|
|
|
|
|
|
def _success_outcome(path: Path):
|
|
return SimpleNamespace(path=path, succeeded=True, error=None)
|
|
|
|
|
|
def test_metadata_magic_command_defaults_to_both_for_single_photo(tmp_path):
|
|
image_path = tmp_path / "photo.jpg"
|
|
image_path.write_bytes(b"test image")
|
|
|
|
with (
|
|
patch("photools3.cli.process_photo") as process_photo,
|
|
patch("photools3.cli.process_batch") as process_batch,
|
|
):
|
|
result = runner.invoke(app, ["metadata-magic", str(image_path)])
|
|
|
|
assert result.exit_code == 0
|
|
process_photo.assert_called_once_with(image_path, ProcessingMode.BOTH)
|
|
process_batch.assert_not_called()
|
|
|
|
|
|
def test_metadata_magic_command_selects_caption_mode(tmp_path):
|
|
image_path = tmp_path / "photo.jpg"
|
|
image_path.write_bytes(b"test image")
|
|
|
|
with patch("photools3.cli.process_photo") as process_photo:
|
|
result = runner.invoke(
|
|
app, ["metadata-magic", str(image_path), "--mode", "captions"]
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
process_photo.assert_called_once_with(image_path, ProcessingMode.CAPTIONS)
|
|
|
|
|
|
def test_metadata_magic_command_batches_multiple_files(tmp_path):
|
|
paths = [tmp_path / "one.jpg", tmp_path / "two.jpg"]
|
|
for path in paths:
|
|
path.write_bytes(b"test image")
|
|
|
|
with (
|
|
patch("photools3.cli.process_photo") as process_photo,
|
|
patch("photools3.cli.process_batch") as process_batch,
|
|
):
|
|
process_batch.return_value = [_success_outcome(path) for path in paths]
|
|
result = runner.invoke(
|
|
app,
|
|
["metadata-magic", *(str(path) for path in paths), "--mode", "keywords"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
process_photo.assert_not_called()
|
|
process_batch.assert_called_once_with(paths, ProcessingMode.KEYWORDS)
|
|
|
|
|
|
def test_metadata_magic_command_expands_directories_recursively(tmp_path):
|
|
root = tmp_path / "photos"
|
|
nested = root / "nested"
|
|
nested.mkdir(parents=True)
|
|
top_level_photo = root / "one.jpg"
|
|
nested_photo = nested / "two.png"
|
|
sidecar = root / "one.jpg.xmp"
|
|
for path in (top_level_photo, nested_photo, sidecar):
|
|
path.write_bytes(b"placeholder")
|
|
|
|
with (
|
|
patch("photools3.cli.process_photo") as process_photo,
|
|
patch("photools3.cli.process_batch") as process_batch,
|
|
):
|
|
process_batch.return_value = [
|
|
_success_outcome(nested_photo),
|
|
_success_outcome(top_level_photo),
|
|
]
|
|
result = runner.invoke(
|
|
app, ["metadata-magic", str(root), "--recursive", "--mode", "both"]
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
process_photo.assert_not_called()
|
|
process_batch.assert_called_once_with(
|
|
[nested_photo, top_level_photo], ProcessingMode.BOTH
|
|
)
|
|
|
|
|
|
def test_metadata_magic_command_does_not_recurse_by_default(tmp_path):
|
|
root = tmp_path / "photos"
|
|
nested = root / "nested"
|
|
nested.mkdir(parents=True)
|
|
top_level_photo = root / "one.jpg"
|
|
nested_photo = nested / "two.jpg"
|
|
top_level_photo.write_bytes(b"placeholder")
|
|
nested_photo.write_bytes(b"placeholder")
|
|
|
|
with (
|
|
patch("photools3.cli.process_photo") as process_photo,
|
|
patch("photools3.cli.process_batch") as process_batch,
|
|
):
|
|
result = runner.invoke(app, ["metadata-magic", str(root)])
|
|
|
|
assert result.exit_code == 0
|
|
process_photo.assert_called_once_with(top_level_photo, ProcessingMode.BOTH)
|
|
process_batch.assert_not_called()
|
|
|
|
|
|
def test_metadata_magic_command_returns_failure_if_any_batch_item_fails(tmp_path):
|
|
paths = [tmp_path / "one.jpg", tmp_path / "two.jpg"]
|
|
for path in paths:
|
|
path.write_bytes(b"test image")
|
|
|
|
with patch("photools3.cli.process_batch") as process_batch:
|
|
process_batch.return_value = [
|
|
_success_outcome(paths[0]),
|
|
SimpleNamespace(
|
|
path=paths[1], succeeded=False, error="RuntimeError: write failed"
|
|
),
|
|
]
|
|
result = runner.invoke(app, ["metadata-magic", *(str(path) for path in paths)])
|
|
|
|
assert result.exit_code == 1
|
|
|
|
|
|
def test_metadata_magic_command_returns_failure_for_single_photo_error(tmp_path):
|
|
image_path = tmp_path / "photo.jpg"
|
|
image_path.write_bytes(b"test image")
|
|
|
|
with patch("photools3.cli.process_photo", side_effect=RuntimeError("LLM error")):
|
|
result = runner.invoke(app, ["metadata-magic", str(image_path)])
|
|
|
|
assert result.exit_code == 1
|
|
|
|
|
|
def test_keywords_alias_is_not_registered():
|
|
result = runner.invoke(app, ["keywords", "photo.jpg"])
|
|
|
|
assert result.exit_code == 2
|
|
assert "No such command 'keywords'" in result.output
|
|
|
|
|
|
def test_old_process_command_name_is_not_registered():
|
|
result = runner.invoke(app, ["process", "photo.jpg"])
|
|
|
|
assert result.exit_code == 2
|
|
assert "No such command 'process'" in result.output
|
|
|
|
|
|
def test_metadata_magic_command_rejects_empty_directory(tmp_path):
|
|
with (
|
|
patch("photools3.cli.process_photo") as process_photo,
|
|
patch("photools3.cli.process_batch") as process_batch,
|
|
):
|
|
result = runner.invoke(app, ["metadata-magic", str(tmp_path)])
|
|
|
|
assert result.exit_code == 1
|
|
process_photo.assert_not_called()
|
|
process_batch.assert_not_called()
|
|
|
|
|
|
def test_quality_command_closes_photo_and_session(tmp_path):
|
|
image_path = tmp_path / "photo.jpg"
|
|
image_path.write_bytes(b"test image")
|
|
|
|
with (
|
|
patch("photools3.cli.LlmSessionQuality") as session_class,
|
|
patch("photools3.cli.Photo") as photo_class,
|
|
):
|
|
result = runner.invoke(app, ["quality", str(image_path)])
|
|
|
|
assert result.exit_code == 0
|
|
session_class.return_value.__enter__.return_value.call.assert_called_once_with(
|
|
[photo_class.return_value.__enter__.return_value]
|
|
)
|
|
session_class.return_value.__exit__.assert_called_once()
|
|
photo_class.return_value.__exit__.assert_called_once()
|
|
|
|
|
|
def test_quality_command_reports_error_and_exits(tmp_path):
|
|
image_path = tmp_path / "photo.jpg"
|
|
|
|
with patch("photools3.cli.Photo", side_effect=FileNotFoundError(image_path)):
|
|
result = runner.invoke(app, ["quality", str(image_path)])
|
|
|
|
assert result.exit_code == 1
|
|
|
|
|
|
def test_version_command():
|
|
result = runner.invoke(app, ["--version"])
|
|
|
|
assert result.exit_code == 0
|
|
assert "photools3 version 0.1.0" in result.stdout
|