@@ -183,6 +183,66 @@ def test_recommend_duration_fallback_no_history(self):
183183 assert result ["recommended_minutes" ] == 25
184184 assert result ["source" ] == "local_rule"
185185
186+ @pytest .mark .asyncio
187+ async def test_transcribe_accepts_feature_kwarg (self ):
188+ """
189+ 回归:transcribe 必须容忍 call_with_fallback 透传的 _feature 关键字。
190+
191+ 云端 Provider 的 transcribe 由 with_retry_and_timeout 装饰并吞掉 _feature,
192+ 但 FallbackProvider 无装饰器,若签名不接受 **kwargs 会抛 TypeError,
193+ 导致 fallback 链最后一环失败、ASR 请求返回 503 而非降级响应。
194+ """
195+ # Arrange
196+ provider = FallbackProvider ()
197+
198+ # Act
199+ result = await provider .transcribe (
200+ audio_base64 = "ZmFrZQ==" , language = "zh" , _feature = "transcribe" ,
201+ )
202+
203+ # Assert
204+ assert result ["model" ] == "fallback"
205+ assert result ["text" ] == ""
206+ assert result ["warning" ] # 必须透传降级提示供客户端识别失败
207+
208+ @pytest .mark .asyncio
209+ async def test_transcribe_fallback_reachable_when_cloud_asr_fails (self ):
210+ """
211+ 回归:云端 ASR 全失败时,fallback 链末端必须能返回降级响应。
212+
213+ 真实故障场景——Qwen 404 + GLM 400 后,若 FallbackProvider.transcribe
214+ 因签名不接受 _feature 抛 TypeError,整个请求会变成 503。
215+ """
216+ # Arrange:模拟 qwen/glm 的 transcribe 均失败,链末端为真实 FallbackProvider
217+ class FailingASRProvider :
218+ def __init__ (self , name ):
219+ self .provider_name = name
220+
221+ async def transcribe (self , * args , ** kwargs ):
222+ raise RuntimeError (f"{ self .provider_name } ASR 上游故障" )
223+
224+ app = MagicMock ()
225+ app .state .providers = {
226+ "qwen" : FailingASRProvider ("qwen" ),
227+ "glm" : FailingASRProvider ("glm" ),
228+ "fallback" : FallbackProvider (),
229+ }
230+
231+ async def _run_chain (provider , model_name ):
232+ # 与 transcribe 路由一致:显式透传 _feature
233+ return await provider .transcribe (
234+ audio_base64 = "ZmFrZQ==" , language = "zh" , model = model_name ,
235+ _feature = "transcribe" ,
236+ )
237+
238+ # Act
239+ result , provider_key = await call_with_fallback (app , "transcribe" , _run_chain )
240+
241+ # Assert
242+ assert provider_key == "fallback"
243+ assert result ["model" ] == "fallback"
244+ assert result ["warning" ]
245+
186246 def test_recommend_duration_fallback_short_history (self ):
187247 history = [
188248 {"duration_minutes" : 10 , "completed" : True },
0 commit comments