Spaces:
Running
Running
File size: 6,216 Bytes
534b431 dfedf76 534b431 dfedf76 534b431 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | """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
|