from __future__ import annotations import builtins import importlib.util import sys import unittest from pathlib import Path from types import ModuleType from unittest.mock import patch import numpy as np BACKEND_DIR = Path(__file__).resolve().parents[1] AUDIO_GENERATOR_PATH = BACKEND_DIR / "audio_generator.py" class AudioGeneratorFallbackTests(unittest.TestCase): def _load_module_with_missing_runtime(self) -> ModuleType: module_name = "audio_generator_missing_runtime_test" spec = importlib.util.spec_from_file_location(module_name, AUDIO_GENERATOR_PATH) if spec is None or spec.loader is None: self.fail("Unable to load audio_generator module for fallback test.") module = importlib.util.module_from_spec(spec) sys.modules[module_name] = module self.addCleanup(sys.modules.pop, module_name, None) original_import = builtins.__import__ def guarded_import( name: str, globals_: dict[str, object] | None = None, locals_: dict[str, object] | None = None, fromlist: tuple[str, ...] = (), level: int = 0, ) -> ModuleType: blocked_roots = {"torch", "transformers", "omnivoice"} if name.split(".", 1)[0] in blocked_roots: raise ImportError(f"blocked import for test: {name}") return original_import(name, globals_, locals_, fromlist, level) with patch("builtins.__import__", side_effect=guarded_import): spec.loader.exec_module(module) return module def test_generator_falls_back_to_mock_when_runtime_stack_is_missing(self) -> None: module = self._load_module_with_missing_runtime() generator = module.AudioGenerator() audio = generator.generate("Fallback should keep the app alive.", duration=1.0) self.assertIsNone(generator.model) self.assertEqual(generator.runtime_backend, "mock") self.assertEqual(generator.runtime_device, "cpu") self.assertEqual(len(audio), 1) self.assertIsInstance(audio[0], np.ndarray) self.assertEqual(audio[0].shape[0], 24000) def test_generator_can_fail_fast_when_real_runtime_is_required(self) -> None: module = self._load_module_with_missing_runtime() with patch.dict(module.os.environ, {"OMNIVOICE_REQUIRE_REAL_RUNTIME": "1"}, clear=False): with self.assertRaises(RuntimeError): module.AudioGenerator()