From bddf99a00262392af5985e5b492425e8654f4d7d Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 26 Apr 2026 20:52:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(test):=20test=5Follama=5Ffailover=5Fmanager?= =?UTF-8?q?=20pipeline=20mock=20=E5=B0=8D=E9=BD=8A=20atomic=20=E4=BF=AE?= =?UTF-8?q?=E5=BE=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wave5 B3-fix(commit 02362edd)改 _check_gemini_quota 用 redis.pipeline() 原測試 mock redis.incr.assert_awaited_once 失敗,因 incr 改在 pipeline 內。 修法(Engineer-A4 已同步寫好): - mock_pipe.set / incr 返回 mock_pipe(chain) - mock_pipe.execute 返回 [True, count] list - assertion 改 mock_pipe.execute.assert_awaited_once Tests: 37/37 PASSED Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: Engineer-A4 --- .../api/tests/test_ollama_failover_manager.py | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/apps/api/tests/test_ollama_failover_manager.py b/apps/api/tests/test_ollama_failover_manager.py index 7e9d545f..54e99cf6 100644 --- a/apps/api/tests/test_ollama_failover_manager.py +++ b/apps/api/tests/test_ollama_failover_manager.py @@ -610,40 +610,49 @@ class TestGeminiQuota: @pytest.mark.asyncio async def test_gemini_quota_under_limit(self): - """count=500 < quota=1000 → 返回 True(允許走 Gemini)""" + """count=500 < quota=1000 → 返回 True(允許走 Gemini) + 2026-04-26 Wave5 B3-fix by Claude Engineer-A4 — 改用 pipeline mock(atomic 修復後) + 原 GET/INCR/EXPIRE 三步已改為 pipeline.set(NX)+incr,mock 跟著更新。 + """ manager = _make_manager() manager._settings.GEMINI_DAILY_QUOTA = 1000 - mock_redis = AsyncMock() - mock_redis.get = AsyncMock(return_value=b"500") - mock_redis.incr = AsyncMock(return_value=501) - mock_redis.expire = AsyncMock() + # pipeline mock:SET NX 返回 True(首次),INCR 返回 501(500+1,未達 quota=1000) + mock_pipe = MagicMock() + mock_pipe.set = MagicMock(return_value=mock_pipe) + mock_pipe.incr = MagicMock(return_value=mock_pipe) + mock_pipe.execute = AsyncMock(return_value=[True, 501]) + + mock_redis = MagicMock() + mock_redis.pipeline = MagicMock(return_value=mock_pipe) - # lazy import patch:_check_gemini_quota 內用 `from src.core.redis_client import get_redis` with patch("src.core.redis_client.get_redis", return_value=mock_redis): ok = await manager._check_gemini_quota() assert ok is True - mock_redis.incr.assert_awaited_once() - mock_redis.expire.assert_awaited_once() + mock_pipe.execute.assert_awaited_once() @pytest.mark.asyncio async def test_gemini_quota_exactly_at_limit(self): - """count=1000 >= quota=1000 → 返回 False(熔斷,不再呼叫 Gemini)""" + """count=1001 > quota=1000 → 返回 False(熔斷,不再呼叫 Gemini) + 2026-04-26 Wave5 B3-fix by Claude Engineer-A4 — 改用 pipeline mock(atomic 修復後) + pipeline.incr 返回 1001(> quota=1000),應返回 False。 + """ manager = _make_manager() manager._settings.GEMINI_DAILY_QUOTA = 1000 - mock_redis = AsyncMock() - mock_redis.get = AsyncMock(return_value=b"1000") - mock_redis.incr = AsyncMock() - mock_redis.expire = AsyncMock() + mock_pipe = MagicMock() + mock_pipe.set = MagicMock(return_value=mock_pipe) + mock_pipe.incr = MagicMock(return_value=mock_pipe) + mock_pipe.execute = AsyncMock(return_value=[True, 1001]) # 超過 quota + + mock_redis = MagicMock() + mock_redis.pipeline = MagicMock(return_value=mock_pipe) with patch("src.core.redis_client.get_redis", return_value=mock_redis): ok = await manager._check_gemini_quota() assert ok is False - # 超過配額不應再 incr - mock_redis.incr.assert_not_awaited() @pytest.mark.asyncio async def test_gemini_quota_redis_unavailable_fail_open(self):