Spaces:
Running
Running
| import importlib | |
| import sys | |
| import types | |
| from unittest import mock | |
| import pytest | |
| def import_app_with_gradio_stub(): | |
| sys.modules.pop("app", None) | |
| sys.modules["gradio"] = types.ModuleType("gradio") | |
| return importlib.import_module("app") | |
| def import_app_without_gradio(): | |
| original_import = __import__ | |
| def blocked_import(name, globals=None, locals=None, fromlist=(), level=0): | |
| if name == "gradio": | |
| raise ModuleNotFoundError("No module named 'gradio'") | |
| return original_import(name, globals, locals, fromlist, level) | |
| sys.modules.pop("app", None) | |
| sys.modules.pop("gradio", None) | |
| with mock.patch("builtins.__import__", side_effect=blocked_import): | |
| return importlib.import_module("app") | |
| def test_normalize_rhythm_override_treats_automatic_as_none(): | |
| app = import_app_with_gradio_stub() | |
| assert app.normalize_rhythm_override("Automatic") is None | |
| def test_session_examples_leave_tone_center_blank_for_automatic_mode(): | |
| app = import_app_with_gradio_stub() | |
| assert all(example[2] is None for example in app.SESSION_EXAMPLES) | |
| def test_build_session_copy_uses_human_labels(): | |
| app = import_app_with_gradio_stub() | |
| analysis = { | |
| "emotional_state": "anxious", | |
| "session_profile": { | |
| "title": "Grounding Tide", | |
| "emotional_tone": "Settling and steady", | |
| "guidance": "Let your breath fall behind the pulse until the session feels steady.", | |
| "reflection": "This session favors stability over intensity.", | |
| }, | |
| } | |
| card = app.build_session_copy(analysis) | |
| assert card["session_name"] == "Grounding Tide" | |
| assert card["listening_path"].startswith("Settling and steady") | |
| assert "stability over intensity" in card["session_reflection"] | |
| def test_import_without_gradio_uses_interface_guard(): | |
| app = import_app_without_gradio() | |
| assert app.gr is None | |
| with pytest.raises(ModuleNotFoundError, match="gradio is required"): | |
| app.create_interface() | |
| def test_rhythma_experience_returns_session_led_copy(monkeypatch): | |
| app = import_app_with_gradio_stub() | |
| analysis = { | |
| "emotional_state": "anxious", | |
| "transcription": "", | |
| "session_profile": { | |
| "title": "Grounding Tide", | |
| "emotional_tone": "Settling and steady", | |
| "guidance": "Let your breath fall behind the pulse until the session feels steady.", | |
| "reflection": "This session favors stability over intensity.", | |
| }, | |
| } | |
| monkeypatch.setattr(app, "analyze_input", lambda text, audio: analysis) | |
| monkeypatch.setattr( | |
| app, | |
| "generate_modulated_experience", | |
| lambda *args, **kwargs: ("legacy analysis", "session.wav", "plot", "image", "legacy symbolic"), | |
| ) | |
| outputs = app.rhythma_experience( | |
| "I feel anxious and need to settle down", | |
| None, | |
| override_freq=0, | |
| override_modulation="sine", | |
| override_rhythm="Automatic", | |
| duration=5, | |
| ) | |
| assert outputs[0] == "### Grounding Tide" | |
| assert outputs[1] == "Settling and steady" | |
| assert outputs[2].startswith("Settling and steady") | |
| assert outputs[3] == "session.wav" | |
| assert outputs[6] == "This session favors stability over intensity." | |
| assert outputs[7] == "" | |
| def test_rhythma_experience_degrades_copy_consistently_on_generation_failure(monkeypatch): | |
| app = import_app_with_gradio_stub() | |
| analysis = { | |
| "emotional_state": "anxious", | |
| "transcription": "", | |
| "session_profile": { | |
| "title": "Grounding Tide", | |
| "emotional_tone": "Settling and steady", | |
| "guidance": "Let your breath fall behind the pulse until the session feels steady.", | |
| "reflection": "This session favors stability over intensity.", | |
| }, | |
| } | |
| monkeypatch.setattr(app, "analyze_input", lambda text, audio: analysis) | |
| monkeypatch.setattr( | |
| app, | |
| "generate_modulated_experience", | |
| lambda *args, **kwargs: ("Generation unavailable", None, None, None, "legacy symbolic"), | |
| ) | |
| outputs = app.rhythma_experience( | |
| "I feel anxious and need to settle down", | |
| None, | |
| override_freq=0, | |
| override_modulation="sine", | |
| override_rhythm="Automatic", | |
| duration=5, | |
| ) | |
| assert outputs[2] == "Generation unavailable" | |
| assert outputs[6] == "Generation unavailable" | |
| def test_rhythma_experience_uses_real_session_profile_across_pipeline(monkeypatch): | |
| app = import_app_with_gradio_stub() | |
| captured = {} | |
| class FakeEngine: | |
| def __init__(self, **kwargs): | |
| captured["init_kwargs"] = kwargs | |
| self.generated_audio = None | |
| def render_session(self, profile, duration): | |
| captured["render_profile"] = profile | |
| captured["render_duration"] = duration | |
| return "layered-session-audio" | |
| def generate_modulated_wave(self, duration): | |
| captured["generated_duration"] = duration | |
| return "legacy-generated-audio" | |
| def save_audio(self, duration, file_path): | |
| captured["save_duration"] = duration | |
| captured["save_path"] = file_path | |
| self.generated_audio = self.generate_modulated_wave(duration) | |
| return file_path | |
| def visualize_waveform(self, duration): | |
| captured["visualize_duration"] = duration | |
| return "plot" | |
| def get_waveform_image(self): | |
| return "image" | |
| def get_complete_analysis(self): | |
| return "legacy analysis" | |
| def get_symbolic_interpretation(self): | |
| return "legacy symbolic" | |
| monkeypatch.setattr(app, "RhythmaModulationEngine", FakeEngine) | |
| outputs = app.rhythma_experience( | |
| "I want to focus on deep work", | |
| None, | |
| override_freq=0, | |
| override_modulation="sine", | |
| override_rhythm="Automatic", | |
| duration=5, | |
| ) | |
| assert outputs[0] == "### Clear Horizon" | |
| assert outputs[1] == "Attentive and composed" | |
| assert outputs[2].startswith("Attentive and composed") | |
| assert outputs[6] == "This session narrows motion to support sustained attention." | |
| assert outputs[7] == "" | |
| assert captured["init_kwargs"] == { | |
| "base_freq": 512.0, | |
| "modulation_type": "sine", | |
| "rhythm_pattern": "focused", | |
| "emotional_state": None, | |
| } | |
| assert captured["render_profile"]["title"] == "Clear Horizon" | |
| assert captured["render_profile"]["tone_center"] == 512.0 | |
| assert captured["render_profile"]["pattern"] == "focused" | |
| assert captured["render_duration"] == 5 | |
| assert captured["save_duration"] == 5 | |
| assert captured["visualize_duration"] == 5 | |
| def test_generate_modulated_experience_prefers_explicit_rhythm_override_without_session_profile(monkeypatch): | |
| app = import_app_with_gradio_stub() | |
| captured = {} | |
| class FakeEngine: | |
| def __init__(self, **kwargs): | |
| captured["init_kwargs"] = kwargs | |
| def save_audio(self, duration, file_path): | |
| return file_path | |
| def visualize_waveform(self, duration): | |
| return "plot" | |
| def get_waveform_image(self): | |
| return "image" | |
| def get_complete_analysis(self): | |
| return "legacy analysis" | |
| def get_symbolic_interpretation(self): | |
| return "legacy symbolic" | |
| monkeypatch.setattr(app, "RhythmaModulationEngine", FakeEngine) | |
| analysis = { | |
| "emotional_state": "neutral", | |
| "rhythm_pattern": "calm", | |
| "transcription": "", | |
| } | |
| result = app.generate_modulated_experience( | |
| analysis, | |
| modulation_type="sine", | |
| rhythm_pattern="focused", | |
| duration=5, | |
| ) | |
| assert result[0] == "legacy analysis" | |
| assert captured["init_kwargs"]["rhythm_pattern"] == "focused" | |