Skip to content

Commit bd69bd9

Browse files
test(images): add tests for branding functions
Add comprehensive tests for og:image branding functions to improve coverage from 29% to 90%: - TestBrandingFunctions: tests for _get_font, _draw_pyplots_logo, create_branded_header, _draw_rounded_card, create_branded_og_image, and create_og_collage with various input types (path, bytes, PIL) - TestBrandingCLI: tests for brand and collage CLI commands 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3749b14 commit bd69bd9

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed

tests/unit/core/test_images.py

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,3 +434,245 @@ def test_cli_process_missing_args(self, monkeypatch, capsys) -> None:
434434

435435
captured = capsys.readouterr()
436436
assert "Usage:" in captured.out
437+
438+
439+
class TestBrandingFunctions:
440+
"""Tests for OG image branding functions."""
441+
442+
@pytest.fixture
443+
def sample_plot_image(self, tmp_path: Path) -> Path:
444+
"""Create a sample plot image for branding tests."""
445+
img_path = tmp_path / "plot.png"
446+
img = Image.new("RGB", (800, 600), color=(100, 150, 200))
447+
img.save(img_path)
448+
return img_path
449+
450+
def test_get_font_fallback(self) -> None:
451+
"""Should return a font (fallback if MonoLisa not available)."""
452+
from core.images import _get_font
453+
454+
font = _get_font(32)
455+
assert font is not None
456+
457+
def test_get_font_with_weight(self) -> None:
458+
"""Should accept weight parameter."""
459+
from core.images import _get_font
460+
461+
font = _get_font(24, weight=400)
462+
assert font is not None
463+
464+
def test_draw_pyplots_logo(self, tmp_path: Path) -> None:
465+
"""Should draw logo with correct colors."""
466+
from PIL import ImageDraw
467+
468+
from core.images import _draw_pyplots_logo
469+
470+
img = Image.new("RGB", (400, 100), color="#f8f9fa")
471+
draw = ImageDraw.Draw(img)
472+
width = _draw_pyplots_logo(draw, 50, 30, font_size=32)
473+
474+
assert width > 0
475+
# Save to verify visually if needed
476+
img.save(tmp_path / "logo_test.png")
477+
assert (tmp_path / "logo_test.png").exists()
478+
479+
def test_create_branded_header(self) -> None:
480+
"""Should create header with correct dimensions."""
481+
from core.images import create_branded_header
482+
483+
header = create_branded_header(width=1200, height=80)
484+
485+
assert header.width == 1200
486+
assert header.height == 80
487+
assert header.mode == "RGB"
488+
489+
def test_draw_rounded_card(self, tmp_path: Path) -> None:
490+
"""Should draw rounded card with shadow."""
491+
from core.images import _draw_rounded_card
492+
493+
base = Image.new("RGBA", (400, 300), "#f8f9fa")
494+
content = Image.new("RGB", (200, 150), "#ffffff")
495+
496+
_draw_rounded_card(base, content, x=50, y=50, padding=10, radius=12)
497+
498+
# Verify image was modified (not just background color)
499+
base.save(tmp_path / "card_test.png")
500+
assert (tmp_path / "card_test.png").exists()
501+
502+
def test_create_branded_og_image_from_path(self, sample_plot_image: Path, tmp_path: Path) -> None:
503+
"""Should create branded OG image from file path."""
504+
from core.images import create_branded_og_image
505+
506+
output_path = tmp_path / "branded.png"
507+
create_branded_og_image(sample_plot_image, output_path, spec_id="test-spec", library="matplotlib")
508+
509+
assert output_path.exists()
510+
img = Image.open(output_path)
511+
assert img.width == 1200
512+
assert img.height == 630
513+
514+
def test_create_branded_og_image_from_bytes(self, sample_plot_image: Path) -> None:
515+
"""Should create branded OG image from bytes and return bytes."""
516+
from io import BytesIO
517+
518+
from core.images import create_branded_og_image
519+
520+
with open(sample_plot_image, "rb") as f:
521+
image_bytes = f.read()
522+
523+
result = create_branded_og_image(image_bytes, spec_id="test-spec", library="matplotlib")
524+
525+
assert isinstance(result, bytes)
526+
# Verify it's a valid PNG
527+
img = Image.open(BytesIO(result))
528+
assert img.width == 1200
529+
assert img.height == 630
530+
531+
def test_create_branded_og_image_from_pil_image(self, tmp_path: Path) -> None:
532+
"""Should create branded OG image from PIL Image."""
533+
from io import BytesIO
534+
535+
from core.images import create_branded_og_image
536+
537+
pil_img = Image.new("RGB", (800, 600), color=(100, 150, 200))
538+
result = create_branded_og_image(pil_img, spec_id="test-spec")
539+
540+
assert isinstance(result, bytes)
541+
img = Image.open(BytesIO(result))
542+
assert img.width == 1200
543+
544+
def test_create_branded_og_image_rgba(self, tmp_path: Path) -> None:
545+
"""Should handle RGBA images."""
546+
from core.images import create_branded_og_image
547+
548+
rgba_img = Image.new("RGBA", (800, 600), color=(100, 150, 200, 128))
549+
result = create_branded_og_image(rgba_img)
550+
551+
assert isinstance(result, bytes)
552+
553+
def test_create_og_collage_single_image(self, sample_plot_image: Path) -> None:
554+
"""Should create collage with single image."""
555+
from io import BytesIO
556+
557+
from core.images import create_og_collage
558+
559+
with open(sample_plot_image, "rb") as f:
560+
image_bytes = f.read()
561+
562+
result = create_og_collage([image_bytes], labels=["test · matplotlib"])
563+
564+
assert isinstance(result, bytes)
565+
img = Image.open(BytesIO(result))
566+
assert img.width == 1200
567+
assert img.height == 630
568+
569+
def test_create_og_collage_multiple_images(self, tmp_path: Path) -> None:
570+
"""Should create collage with multiple images."""
571+
from io import BytesIO
572+
573+
from core.images import create_og_collage
574+
575+
# Create multiple test images
576+
images = []
577+
labels = []
578+
for i in range(6):
579+
img = Image.new("RGB", (400, 300), color=(100 + i * 20, 150, 200))
580+
buf = BytesIO()
581+
img.save(buf, "PNG")
582+
images.append(buf.getvalue())
583+
labels.append(f"test · lib{i}")
584+
585+
result = create_og_collage(images, labels=labels)
586+
587+
assert isinstance(result, bytes)
588+
img = Image.open(BytesIO(result))
589+
assert img.width == 1200
590+
assert img.height == 630
591+
592+
def test_create_og_collage_to_file(self, sample_plot_image: Path, tmp_path: Path) -> None:
593+
"""Should save collage to file."""
594+
from core.images import create_og_collage
595+
596+
output_path = tmp_path / "collage.png"
597+
create_og_collage([sample_plot_image], output_path=output_path)
598+
599+
assert output_path.exists()
600+
img = Image.open(output_path)
601+
assert img.width == 1200
602+
603+
def test_create_og_collage_empty_raises(self) -> None:
604+
"""Should raise ValueError for empty image list."""
605+
from core.images import create_og_collage
606+
607+
with pytest.raises(ValueError, match="At least one image"):
608+
create_og_collage([])
609+
610+
def test_create_og_collage_without_labels(self, sample_plot_image: Path) -> None:
611+
"""Should work without labels."""
612+
613+
from core.images import create_og_collage
614+
615+
result = create_og_collage([sample_plot_image])
616+
617+
assert isinstance(result, bytes)
618+
619+
620+
class TestBrandingCLI:
621+
"""Tests for branding CLI commands."""
622+
623+
@pytest.fixture(autouse=True)
624+
def clean_module_cache(self):
625+
"""Remove core.images from sys.modules to avoid runpy warning."""
626+
import sys
627+
628+
sys.modules.pop("core.images", None)
629+
yield
630+
sys.modules.pop("core.images", None)
631+
632+
@pytest.fixture
633+
def sample_plot_image(self, tmp_path: Path) -> Path:
634+
"""Create a sample plot image."""
635+
img_path = tmp_path / "plot.png"
636+
img = Image.new("RGB", (800, 600), color=(100, 150, 200))
637+
img.save(img_path)
638+
return img_path
639+
640+
def test_cli_brand_command(self, sample_plot_image: Path, tmp_path: Path, monkeypatch, capsys) -> None:
641+
"""Should run brand command from CLI."""
642+
import sys
643+
644+
output_path = tmp_path / "branded.png"
645+
646+
monkeypatch.setattr(
647+
sys, "argv", ["images", "brand", str(sample_plot_image), str(output_path), "test-spec", "matplotlib"]
648+
)
649+
650+
import runpy
651+
652+
try:
653+
runpy.run_module("core.images", run_name="__main__", alter_sys=True)
654+
except SystemExit:
655+
pass
656+
657+
assert output_path.exists()
658+
captured = capsys.readouterr()
659+
assert "1200x630" in captured.out
660+
661+
def test_cli_collage_command(self, sample_plot_image: Path, tmp_path: Path, monkeypatch, capsys) -> None:
662+
"""Should run collage command from CLI."""
663+
import sys
664+
665+
output_path = tmp_path / "collage.png"
666+
667+
monkeypatch.setattr(sys, "argv", ["images", "collage", str(output_path), str(sample_plot_image)])
668+
669+
import runpy
670+
671+
try:
672+
runpy.run_module("core.images", run_name="__main__", alter_sys=True)
673+
except SystemExit:
674+
pass
675+
676+
assert output_path.exists()
677+
captured = capsys.readouterr()
678+
assert "Collage" in captured.out

0 commit comments

Comments
 (0)