Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions multi_llm_chatbot_backend/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,23 @@
# Pydantic models
# ---------------------------------------------------------------------------

class FeatureConfig(BaseModel):
class _IconValidatorMixin(BaseModel):
"""Validates that the ``icon`` field is a known Lucide icon name."""

@model_validator(mode="after")
def _validate_icon(self):
from app.utils.lucide_icons import get_valid_icon_names

valid = get_valid_icon_names()
if valid and self.icon not in valid:
raise ValueError(
f"Unknown icon {self.icon!r}. "
f"Must be a valid Lucide icon name."
)
return self


class FeatureConfig(_IconValidatorMixin):
title: str = ""
description: str = ""
icon: str = "HelpCircle"
Expand Down Expand Up @@ -53,7 +69,7 @@ class LoginConfig(BaseModel):
academic_stages: List[AcademicStage] = []


class ExampleCategory(BaseModel):
class ExampleCategory(_IconValidatorMixin):
title: str
icon: str = "BookOpen"
color: str = "#3B82F6"
Expand All @@ -66,7 +82,7 @@ class ChatPageConfig(BaseModel):
examples: List[ExampleCategory] = []


class PersonaItemConfig(BaseModel):
class PersonaItemConfig(_IconValidatorMixin):
id: str
name: str
enabled: bool = True
Expand Down
74 changes: 74 additions & 0 deletions multi_llm_chatbot_backend/app/tests/test_lucide_icons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import pytest
from pydantic import ValidationError
from app.utils.lucide_icons import get_valid_icon_names
from app.config import FeatureConfig, ExampleCategory, PersonaItemConfig


@pytest.fixture(autouse=True)
def _clear_icon_cache():
get_valid_icon_names.cache_clear()
yield
get_valid_icon_names.cache_clear()


# ---------------------------------------------------------------------------
# Icon registry tests
# ---------------------------------------------------------------------------


def test_returns_known_icons():
icons = get_valid_icon_names()
assert len(icons) > 1500
assert "HelpCircle" in icons
assert "BookOpen" in icons


def test_rejects_invalid_icon():
icons = get_valid_icon_names()
assert "NotARealIcon" not in icons


# ---------------------------------------------------------------------------
# Icon validation tests
# ---------------------------------------------------------------------------


def test_feature_config_accepts_valid_icon():
feature = FeatureConfig(title="Test", description="desc", icon="BookOpen")
assert feature.icon == "BookOpen"


def test_example_category_accepts_valid_icon():
example = ExampleCategory(title="Test", icon="Brain")
assert example.icon == "Brain"


def test_persona_item_accepts_valid_icon():
persona = PersonaItemConfig(id="test", name="Test", icon="Heart")
assert persona.icon == "Heart"


def test_default_icons_are_valid():
feature = FeatureConfig(title="Test", description="desc")
assert feature.icon == "HelpCircle"

example = ExampleCategory(title="Test")
assert example.icon == "BookOpen"

persona = PersonaItemConfig(id="test", name="Test")
assert persona.icon == "HelpCircle"


def test_feature_config_rejects_invalid_icon():
with pytest.raises(ValidationError, match="Unknown icon"):
FeatureConfig(title="Test", description="desc", icon="NotARealIcon")


def test_example_category_rejects_invalid_icon():
with pytest.raises(ValidationError, match="Unknown icon"):
ExampleCategory(title="Test", icon="TotallyFake")


def test_persona_item_rejects_invalid_icon():
with pytest.raises(ValidationError, match="Unknown icon"):
PersonaItemConfig(id="test", name="Test", icon="Nope")
Loading