Spaces:
Running
Running
| """Tests for transcript analysis loader.""" | |
| import types | |
| import yaml | |
| from lyon_chatbox.cascade.transcript_analysis import loader as loader_mod | |
| from lyon_chatbox.cascade.transcript_analysis.base import TriggerConfig | |
| from lyon_chatbox.cascade.transcript_analysis.loader import _parse_trigger | |
| # --- _parse_trigger --- | |
| def test_parse_trigger_words_only(): | |
| """Parse trigger with only words.""" | |
| t = _parse_trigger({"words": ["guitar", "bass"]}) | |
| assert t.words == ["guitar", "bass"] | |
| assert t.entities == [] | |
| assert t.all == [] | |
| def test_parse_trigger_entities_only(): | |
| """Parse trigger with only entities.""" | |
| t = _parse_trigger({"entities": ["PERSON", "ORG"]}) | |
| assert t.entities == ["PERSON", "ORG"] | |
| assert t.words == [] | |
| def test_parse_trigger_all_groups(): | |
| """Parse trigger with nested all sub-triggers.""" | |
| t = _parse_trigger({"all": [{"words": ["danc*"]}, {"words": ["groov*"]}]}) | |
| assert len(t.all) == 2 | |
| assert t.all[0].words == ["danc*"] | |
| assert t.all[1].words == ["groov*"] | |
| assert t.words == [] | |
| def test_parse_trigger_empty_dict(): | |
| """Parse empty dict into default TriggerConfig.""" | |
| t = _parse_trigger({}) | |
| assert t == TriggerConfig() | |
| # --- _import_callback --- | |
| def test_import_callback_success(monkeypatch): | |
| """Return function when import and getattr succeed.""" | |
| def fake_fn(): | |
| pass | |
| fake_module = types.ModuleType("fake") | |
| setattr(fake_module, "my_callback", fake_fn) | |
| monkeypatch.setattr("importlib.import_module", lambda path: fake_module) | |
| result = loader_mod._import_callback("test_profile", "my_callback") | |
| assert result is fake_fn | |
| def test_import_callback_import_error(monkeypatch): | |
| """Return None when module import fails.""" | |
| monkeypatch.setattr("importlib.import_module", lambda path: (_ for _ in ()).throw(ImportError("no module"))) | |
| result = loader_mod._import_callback("test_profile", "missing_mod") | |
| assert result is None | |
| def test_import_callback_missing_attribute(monkeypatch): | |
| """Return None when module lacks the callback attribute.""" | |
| fake_module = types.ModuleType("fake") | |
| monkeypatch.setattr("importlib.import_module", lambda path: fake_module) | |
| result = loader_mod._import_callback("test_profile", "nonexistent") | |
| assert result is None | |
| # --- get_profile_reactions --- | |
| def _setup_profile(monkeypatch, tmp_path, profile_name, yaml_content=None, callback_fn=None): | |
| """Set up a fake profile directory with optional YAML and callback.""" | |
| monkeypatch.setattr(loader_mod, "PROFILES_DIRECTORY", tmp_path) | |
| # Monkeypatch config object | |
| config_obj = types.SimpleNamespace(LYON_CHATBOX_CUSTOM_PROFILE=profile_name) | |
| monkeypatch.setattr(loader_mod, "config", config_obj) | |
| if profile_name: | |
| profile_dir = tmp_path / profile_name | |
| profile_dir.mkdir(parents=True, exist_ok=True) | |
| if yaml_content is not None: | |
| reactions_file = profile_dir / "reactions.yaml" | |
| reactions_file.write_text(yaml.dump(yaml_content)) | |
| if callback_fn is not None: | |
| monkeypatch.setattr(loader_mod, "_import_callback", callback_fn) | |
| def test_no_profile_returns_none(monkeypatch, tmp_path): | |
| """Return None when no profile is configured.""" | |
| _setup_profile(monkeypatch, tmp_path, profile_name=None) | |
| assert loader_mod.get_profile_reactions() is None | |
| def test_no_reactions_yaml_returns_none(monkeypatch, tmp_path): | |
| """Return None when profile dir exists but has no reactions.yaml.""" | |
| _setup_profile(monkeypatch, tmp_path, profile_name="myprofile") | |
| assert loader_mod.get_profile_reactions() is None | |
| def test_valid_yaml_loads_reactions(monkeypatch, tmp_path): | |
| """Load a complete reaction from valid YAML.""" | |
| async def fake_cb(deps, match): | |
| pass | |
| _setup_profile( | |
| monkeypatch, tmp_path, profile_name="myprofile", | |
| yaml_content=[{ | |
| "name": "greet", | |
| "callback": "greet", | |
| "trigger": {"words": ["hello", "hi"]}, | |
| }], | |
| callback_fn=lambda profile, name: fake_cb, | |
| ) | |
| reactions = loader_mod.get_profile_reactions() | |
| assert reactions is not None | |
| assert len(reactions) == 1 | |
| assert reactions[0].name == "greet" | |
| assert reactions[0].trigger.words == ["hello", "hi"] | |
| assert reactions[0].callback is fake_cb | |
| def test_skips_entry_missing_name(monkeypatch, tmp_path): | |
| """Skip YAML entries that lack a name field.""" | |
| async def fake_cb(deps, match): | |
| pass | |
| _setup_profile( | |
| monkeypatch, tmp_path, profile_name="myprofile", | |
| yaml_content=[{"callback": "something", "trigger": {"words": ["x"]}}], | |
| callback_fn=lambda profile, name: fake_cb, | |
| ) | |
| assert loader_mod.get_profile_reactions() is None | |
| def test_skips_entry_bad_callback(monkeypatch, tmp_path): | |
| """Skip entries whose callback fails to import.""" | |
| _setup_profile( | |
| monkeypatch, tmp_path, profile_name="myprofile", | |
| yaml_content=[{ | |
| "name": "broken", | |
| "callback": "broken", | |
| "trigger": {"words": ["x"]}, | |
| }], | |
| callback_fn=lambda profile, name: None, | |
| ) | |
| assert loader_mod.get_profile_reactions() is None | |
| def test_empty_yaml_returns_none(monkeypatch, tmp_path): | |
| """Return None when YAML contains an empty list.""" | |
| _setup_profile( | |
| monkeypatch, tmp_path, profile_name="myprofile", | |
| yaml_content=[], | |
| ) | |
| assert loader_mod.get_profile_reactions() is None | |
| def test_preserves_params_and_repeatable(monkeypatch, tmp_path): | |
| """Preserve params dict and repeatable flag from YAML.""" | |
| async def fake_cb(deps, match): | |
| pass | |
| _setup_profile( | |
| monkeypatch, tmp_path, profile_name="myprofile", | |
| yaml_content=[{ | |
| "name": "wave", | |
| "callback": "wave", | |
| "trigger": {"words": ["wave"]}, | |
| "params": {"direction": "left", "speed": 2}, | |
| "repeatable": True, | |
| }], | |
| callback_fn=lambda profile, name: fake_cb, | |
| ) | |
| reactions = loader_mod.get_profile_reactions() | |
| assert reactions is not None | |
| r = reactions[0] | |
| assert r.params == {"direction": "left", "speed": 2} | |
| assert r.repeatable is True | |