@@ -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