import asyncio import uuid from unittest.mock import AsyncMock, Mock from models.sql.slide import SlideModel from services.icon_finder_service import IconFinderService from templates.presentation_layout import PresentationLayoutModel, SlideLayoutModel from utils import process_slides from utils.icon_weights import extract_icon_weight_from_settings, normalize_icon_weight def test_icon_weight_settings_uses_only_icon_weight_and_fallback(): assert extract_icon_weight_from_settings({"icon_weight": "thin"}) == "thin" assert extract_icon_weight_from_settings({"wrong_key": "thin"}) == "bold" assert extract_icon_weight_from_settings({"icon_weight": "unknown"}) == "bold" assert normalize_icon_weight(None) == "bold" def test_presentation_layout_reads_template_icon_weight(): layout = PresentationLayoutModel( name="general", ordered=False, icon_weight="duotone", slides=[SlideLayoutModel(id="intro", json_schema={"title": "Intro"})], ) assert layout.icon_weight == "duotone" def test_icon_finder_builds_weighted_static_urls(monkeypatch): service = IconFinderService() monkeypatch.setattr( "services.icon_finder_service.get_resource_path", lambda path: f"/app/{path}", ) monkeypatch.setattr( "services.icon_finder_service.os.path.isfile", lambda path: True, ) regular_url = service._icon_url_for_weight( "chart-line-up-bold||chart growth", "regular", ) thin_url = service._icon_url_for_weight("chart-line-up-bold", "thin") assert regular_url.endswith("/static/icons/regular/chart-line-up.svg") assert thin_url.endswith("/static/icons/thin/chart-line-up-thin.svg") def test_process_slide_fetches_icons_with_template_weight(monkeypatch): captured = {} async def fake_search_icons(query, k=1, weight=None): captured["query"] = query captured["weight"] = weight return [f"/static/icons/{weight}/checks-{weight}.svg"] monkeypatch.setattr( process_slides.ICON_FINDER_SERVICE, "search_icons", fake_search_icons, ) slide = SlideModel( presentation=uuid.uuid4(), layout_group="general", layout="layout-1", index=0, content={ "icon": { "__icon_query__": "success check", "__icon_url__": "/static/icons/placeholder.svg", } }, properties=None, ) assets = asyncio.run( process_slides.process_slide_and_fetch_assets( image_generation_service=Mock(), slide=slide, icon_weight="thin", ) ) assert assets == [] assert captured == {"query": "success check", "weight": "thin"} assert slide.content["icon"]["__icon_url__"].endswith( "/static/icons/thin/checks-thin.svg" ) def test_process_slide_fetches_every_template_v2_image_and_icon(monkeypatch): icon_queries = [] async def fake_search_icons(query, k=1, weight=None): icon_queries.append(query) return [f"/static/icons/{query.replace(' ', '-')}.svg"] monkeypatch.setattr( process_slides.ICON_FINDER_SERVICE, "search_icons", fake_search_icons, ) image_generation_service = Mock() image_generation_service.generate_image = AsyncMock( side_effect=[ "/static/generated/market.png", "/static/generated/team.png", ] ) slide = SlideModel( presentation=uuid.uuid4(), layout_group="template-v2", layout="layout-1", index=0, content={ "hero": {"image_prompt": "market dashboard"}, "cards": [ {"image_prompt": "collaborative team"}, {"icon_query": "growth chart"}, {"icon_query": "customer support"}, ], }, properties=None, ) assets = asyncio.run( process_slides.process_slide_and_fetch_assets( image_generation_service=image_generation_service, slide=slide, icon_weight="regular", ) ) assert assets == [] assert [ call.args[0].prompt for call in image_generation_service.generate_image.await_args_list ] == ["market dashboard", "collaborative team"] assert icon_queries == ["growth chart", "customer support"] assert slide.content["hero"]["image_url"].endswith( "/static/generated/market.png" ) assert slide.content["cards"][0]["image_url"].endswith( "/static/generated/team.png" ) assert slide.content["cards"][1]["icon_url"].endswith( "/static/icons/growth-chart.svg" ) assert slide.content["cards"][2]["icon_url"].endswith( "/static/icons/customer-support.svg" ) assert "__image_url__" not in slide.content["hero"] assert "__image_url__" not in slide.content["cards"][0] assert "__icon_url__" not in slide.content["cards"][1] assert "__icon_url__" not in slide.content["cards"][2] def test_process_template_v2_edit_reuses_assets_with_clean_url_fields(monkeypatch): search_icons = AsyncMock() monkeypatch.setattr( process_slides.ICON_FINDER_SERVICE, "search_icons", search_icons, ) image_generation_service = Mock() image_generation_service.generate_image = AsyncMock() old_content = { "hero": { "image_prompt": "market dashboard", "__image_url__": "/static/generated/market.png", }, "badge": { "icon_query": "growth chart", "__icon_url__": "/static/icons/growth-chart.svg", }, } new_content = { "hero": {"image_prompt": "market dashboard"}, "badge": {"icon_query": "growth chart"}, } assets = asyncio.run( process_slides.process_old_and_new_slides_and_fetch_assets( image_generation_service=image_generation_service, old_slide_content=old_content, new_slide_content=new_content, use_template_v2_asset_fields=True, ) ) assert assets == [] image_generation_service.generate_image.assert_not_awaited() search_icons.assert_not_awaited() assert new_content["hero"] == { "image_prompt": "market dashboard", "image_url": "/static/generated/market.png", } assert new_content["badge"] == { "icon_query": "growth chart", "icon_url": "/static/icons/growth-chart.svg", } def test_template_v2_placeholders_use_clean_url_fields(): slide = SlideModel( presentation=uuid.uuid4(), layout_group="template-v2-template-id", layout="layout-1", index=0, content={ "hero": { "image_prompt": "market dashboard", "__image_url__": "/old-image.png", }, "badge": { "icon_query": "growth chart", "__icon_url__": "/old-icon.svg", }, }, properties=None, ) process_slides.process_slide_add_placeholder_assets(slide) assert slide.content["hero"]["image_url"].endswith( "/static/images/placeholder.jpg" ) assert slide.content["badge"]["icon_url"].endswith( "/static/icons/placeholder.svg" ) assert "__image_url__" not in slide.content["hero"] assert "__icon_url__" not in slide.content["badge"]