| 1 | """Integration tests for MiniMax provider support. |
| 2 | |
| 3 | These tests verify provider preset resolution and default pipeline config |
| 4 | loading. They mock the LangChain factory so no real API calls are made. |
| 5 | |
| 6 | Heavy multimedia dependencies (moviepy, scenedetect, cv2, google-genai, |
| 7 | etc.) are stubbed in setUpModule and restored in tearDownModule. Stubbing |
| 8 | at import time leaked the MagicMocks into sys.modules for every test module |
| 9 | collected after this one, making suite results import-order dependent. |
| 10 | """ |
| 11 | |
| 12 | import importlib |
| 13 | import os |
| 14 | import sys |
| 15 | import types |
| 16 | import unittest |
| 17 | from unittest.mock import patch, MagicMock |
| 18 | |
| 19 | _STUB_MODULES = [ |
| 20 | "moviepy", "cv2", "scenedetect", "scenedetect.detectors", |
| 21 | "PIL", "PIL.Image", |
| 22 | "faiss", |
| 23 | "google", "google.genai", "google.genai.types", "google.genai.errors", |
| 24 | "langchain_community", "langchain_community.vectorstores", |
| 25 | "langchain_community.vectorstores.FAISS", |
| 26 | ] |
| 27 | _saved = {} |
| 28 | _modules_before_stubs = set() |
| 29 | |
| 30 | |
| 31 | def setUpModule(): |
| 32 | _modules_before_stubs.update(sys.modules) |
| 33 | for _mod in _STUB_MODULES: |
| 34 | _saved[_mod] = sys.modules.get(_mod) |
| 35 | mock = MagicMock() |
| 36 | # Give stub a __spec__ so importlib.util.find_spec() works |
| 37 | mock.__spec__ = importlib.machinery.ModuleSpec(_mod, None) |
| 38 | mock.__path__ = [] |
| 39 | sys.modules[_mod] = mock |
| 40 | |
| 41 | |
| 42 | def tearDownModule(): |
| 43 | # Drop project modules that were first imported while the stubs were |
| 44 | # active, so later test modules import them fresh against real libraries. |
| 45 | for name in list(sys.modules): |
| 46 | if name in _modules_before_stubs: |
| 47 | continue |
| 48 | if name.split(".")[0] in {"pipelines", "agents", "tools", "interfaces"}: |
| 49 | del sys.modules[name] |
| 50 | for _mod, original in _saved.items(): |
| 51 | if original is None: |
| 52 | sys.modules.pop(_mod, None) |
| 53 | else: |
| 54 | sys.modules[_mod] = original |
| 55 | _saved.clear() |
| 56 | _modules_before_stubs.clear() |
| 57 | |
| 58 | |
| 59 | from utils.provider_presets import resolve_chat_model_config |
| 60 | |
| 61 | |
| 62 | class TestPipelineConfigResolution(unittest.TestCase): |
| 63 | """Integration: config dict -> resolve -> init_chat_model kwargs.""" |
| 64 | |
| 65 | def _make_minimax_config(self, **overrides): |
| 66 | base = { |
| 67 | "model": "MiniMax-M3", |
| 68 | "model_provider": "minimax", |
| 69 | "api_key": "test-key", |
| 70 | } |
| 71 | base.update(overrides) |
| 72 | return base |
| 73 | |
| 74 | def test_full_minimax_config_resolution(self): |
| 75 | config = self._make_minimax_config() |
| 76 | resolved = resolve_chat_model_config(config) |
| 77 | self.assertEqual(resolved["model_provider"], "openai") |
| 78 | self.assertEqual(resolved["base_url"], "https://api.minimax.io/v1") |
| 79 | self.assertEqual(resolved["model"], "MiniMax-M3") |
| 80 | self.assertEqual(resolved["api_key"], "test-key") |
| 81 | |
| 82 | def test_minimax_highspeed_model(self): |
| 83 | config = self._make_minimax_config(model="MiniMax-M2.7-highspeed") |
| 84 | resolved = resolve_chat_model_config(config) |
| 85 | self.assertEqual(resolved["model"], "MiniMax-M2.7-highspeed") |
| 86 | self.assertEqual(resolved["model_provider"], "openai") |
| 87 | |
| 88 | def test_minimax_m27_model(self): |
| 89 | config = self._make_minimax_config(model="MiniMax-M2.7") |
| 90 | resolved = resolve_chat_model_config(config) |
| 91 | self.assertEqual(resolved["model"], "MiniMax-M2.7") |
| 92 | |
| 93 | @patch.dict(os.environ, {"MINIMAX_API_KEY": "env-api-key"}) |
| 94 | def test_env_key_fallback_in_config(self): |
| 95 | config = { |
| 96 | "model": "MiniMax-M3", |
| 97 | "model_provider": "minimax", |
| 98 | } |
| 99 | resolved = resolve_chat_model_config(config) |
| 100 | self.assertEqual(resolved["api_key"], "env-api-key") |
| 101 | |
| 102 | def test_openrouter_config_unchanged(self): |
| 103 | """Existing OpenRouter configs must not be affected.""" |
| 104 | config = { |
| 105 | "model": "google/gemini-2.5-flash-lite-preview-09-2025", |
| 106 | "model_provider": "openai", |
| 107 | "api_key": "or-key", |
| 108 | "base_url": "https://openrouter.ai/api/v1", |
| 109 | } |
| 110 | resolved = resolve_chat_model_config(config) |
| 111 | self.assertEqual(resolved["model_provider"], "openai") |
| 112 | self.assertEqual(resolved["base_url"], "https://openrouter.ai/api/v1") |
| 113 | self.assertEqual(resolved["model"], "google/gemini-2.5-flash-lite-preview-09-2025") |
| 114 | |
| 115 | def test_init_chat_model_receives_openai_provider(self): |
| 116 | """Verify that resolved kwargs have model_provider='openai'.""" |
| 117 | config = self._make_minimax_config() |
| 118 | resolved = resolve_chat_model_config(config) |
| 119 | self.assertEqual(resolved["model_provider"], "openai") |
| 120 | self.assertEqual(resolved["base_url"], "https://api.minimax.io/v1") |
| 121 | self.assertEqual(resolved["model"], "MiniMax-M3") |
| 122 | |
| 123 | def test_temperature_clamping_in_pipeline_flow(self): |
| 124 | config = self._make_minimax_config(temperature=2.0) |
| 125 | resolved = resolve_chat_model_config(config) |
| 126 | self.assertEqual(resolved["temperature"], 1.0) |
| 127 | |
| 128 | def test_extra_kwargs_preserved(self): |
| 129 | config = self._make_minimax_config(max_tokens=4096, top_p=0.9) |
| 130 | resolved = resolve_chat_model_config(config) |
| 131 | self.assertEqual(resolved["max_tokens"], 4096) |
| 132 | self.assertEqual(resolved["top_p"], 0.9) |
| 133 | |
| 134 | |
| 135 | class TestPipelineInitFromConfig(unittest.TestCase): |
| 136 | """Integration: full pipeline init_from_config with provider configs.""" |
| 137 | |
| 138 | @patch("pipelines.idea2video_pipeline.init_chat_model") |
| 139 | @patch("pipelines.idea2video_pipeline.RenderBackend.from_config") |
| 140 | def test_idea2video_pipeline_minimax_config(self, mock_backend, mock_init): |
| 141 | mock_model = MagicMock() |
| 142 | mock_init.return_value = mock_model |
| 143 | mock_backend.return_value = MagicMock(image_generator=MagicMock(), video_generator=MagicMock()) |
| 144 | |
| 145 | from pipelines.idea2video_pipeline import Idea2VideoPipeline |
| 146 | pipeline = Idea2VideoPipeline.init_from_config("configs/idea2video_minimax.yaml") |
| 147 | |
| 148 | mock_init.assert_called_once() |
| 149 | call_kwargs = mock_init.call_args[1] |
| 150 | self.assertEqual(call_kwargs["model_provider"], "openai") |
| 151 | self.assertEqual(call_kwargs["base_url"], "https://api.minimax.io/v1") |
| 152 | self.assertEqual(call_kwargs["model"], "MiniMax-M3") |
| 153 | |
| 154 | @patch("pipelines.script2video_pipeline.init_chat_model") |
| 155 | @patch("pipelines.script2video_pipeline.RenderBackend.from_config") |
| 156 | def test_script2video_pipeline_minimax_config(self, mock_backend, mock_init): |
| 157 | mock_model = MagicMock() |
| 158 | mock_init.return_value = mock_model |
| 159 | mock_backend.return_value = MagicMock(image_generator=MagicMock(), video_generator=MagicMock()) |
| 160 | |
| 161 | from pipelines.script2video_pipeline import Script2VideoPipeline |
| 162 | pipeline = Script2VideoPipeline.init_from_config("configs/script2video_minimax.yaml") |
| 163 | |
| 164 | mock_init.assert_called_once() |
| 165 | call_kwargs = mock_init.call_args[1] |
| 166 | self.assertEqual(call_kwargs["model_provider"], "openai") |
| 167 | self.assertEqual(call_kwargs["base_url"], "https://api.minimax.io/v1") |
| 168 | self.assertEqual(call_kwargs["model"], "MiniMax-M3") |
| 169 | """Integration: full pipeline init_from_config with default configs.""" |
| 170 | |
| 171 | @patch("pipelines.idea2video_pipeline.init_chat_model") |
| 172 | @patch("pipelines.idea2video_pipeline.RenderBackend.from_config") |
| 173 | def test_existing_openrouter_config_still_works(self, mock_backend, mock_init): |
| 174 | mock_model = MagicMock() |
| 175 | mock_init.return_value = mock_model |
| 176 | mock_backend.return_value = MagicMock(image_generator=MagicMock(), video_generator=MagicMock()) |
| 177 | |
| 178 | from pipelines.idea2video_pipeline import Idea2VideoPipeline |
| 179 | pipeline = Idea2VideoPipeline.init_from_config("configs/idea2video.yaml") |
| 180 | |
| 181 | mock_init.assert_called_once() |
| 182 | call_kwargs = mock_init.call_args[1] |
| 183 | self.assertEqual(call_kwargs["model_provider"], "openai") |
| 184 | self.assertEqual(call_kwargs["base_url"], "https://openrouter.ai/api/v1") |
| 185 | |
| 186 | |
| 187 | if __name__ == "__main__": |
| 188 | unittest.main() |
| 189 |