mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
refactor: 继续拆分大型模块并增强模块注册健壮性
后端: - chat_handler_base 错误处理函数提取到 chat_error_utils 子模块 - CLI mixin 引入 CliHandlerProtocol 协议类改善类型标注 - aware_scheduler 拆分为 _candidate_builder 和 _candidate_sorter 子模块 - usage recording 拆分为 _billing_integration 和 _recording_helpers 子模块 - ModuleRegistry 添加循环依赖检测,将写操作从查询方法分离到 reconcile_module_state - 修正 plugin manager 入度注释 前端: - 路由守卫逻辑拆分为独立 guards 模块 - ProviderManagement 拆分为 TableHeader/TableRow/BalanceCell/MobileCard 子组件 - SystemSettings 拆分为多个 Section 子组件和 composables - 提取 useEndpointStatus/useProviderBalance/useProviderFilters composables 测试适配重构后的子模块结构
This commit is contained in:
@@ -69,7 +69,7 @@ async def test_list_all_candidates_returns_provider_batch_count_even_when_candid
|
||||
global_model = _make_global_model(gid="gm1", name="gpt-4o")
|
||||
|
||||
with patch.object(scheduler, "_ensure_initialized", new=AsyncMock(return_value=None)):
|
||||
with patch.object(scheduler, "_query_providers", return_value=providers):
|
||||
with patch.object(scheduler._candidate_builder, "_query_providers", return_value=providers):
|
||||
with patch(
|
||||
"src.services.cache.aware_scheduler.ModelCacheService.get_global_model_by_name",
|
||||
new=AsyncMock(return_value=global_model),
|
||||
@@ -106,7 +106,7 @@ async def test_list_all_candidates_returns_zero_provider_batch_count_when_provid
|
||||
global_model = _make_global_model(gid="gm1", name="gpt-4o")
|
||||
|
||||
with patch.object(scheduler, "_ensure_initialized", new=AsyncMock(return_value=None)):
|
||||
with patch.object(scheduler, "_query_providers", return_value=[]):
|
||||
with patch.object(scheduler._candidate_builder, "_query_providers", return_value=[]):
|
||||
with patch(
|
||||
"src.services.cache.aware_scheduler.ModelCacheService.get_global_model_by_name",
|
||||
new=AsyncMock(return_value=global_model),
|
||||
|
||||
@@ -22,7 +22,7 @@ class TestCheckModelSupportForGlobalModel:
|
||||
"""测试 _check_model_support_for_global_model 方法"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_provider_without_model_should_return_false(self):
|
||||
async def test_provider_without_model_should_return_false(self) -> None:
|
||||
"""Provider 没有配置对应的 Model 时应该返回 False"""
|
||||
scheduler = CacheAwareScheduler()
|
||||
|
||||
@@ -45,7 +45,7 @@ class TestCheckModelSupportForGlobalModel:
|
||||
|
||||
with patch("sqlalchemy.inspect", return_value=mock_inspect):
|
||||
is_supported, skip_reason, caps, provider_model_names = (
|
||||
await scheduler._check_model_support_for_global_model(
|
||||
await scheduler._candidate_builder._check_model_support_for_global_model(
|
||||
db=db,
|
||||
provider=provider,
|
||||
global_model=global_model,
|
||||
@@ -59,7 +59,7 @@ class TestCheckModelSupportForGlobalModel:
|
||||
assert provider_model_names is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_provider_with_different_model_should_return_false(self):
|
||||
async def test_provider_with_different_model_should_return_false(self) -> None:
|
||||
"""Provider 配置了其他模型但没有目标模型时应该返回 False"""
|
||||
scheduler = CacheAwareScheduler()
|
||||
|
||||
@@ -93,7 +93,7 @@ class TestCheckModelSupportForGlobalModel:
|
||||
|
||||
with patch("sqlalchemy.inspect", return_value=mock_inspect):
|
||||
is_supported, skip_reason, caps, provider_model_names = (
|
||||
await scheduler._check_model_support_for_global_model(
|
||||
await scheduler._candidate_builder._check_model_support_for_global_model(
|
||||
db=db,
|
||||
provider=provider,
|
||||
global_model=global_model,
|
||||
@@ -105,7 +105,7 @@ class TestCheckModelSupportForGlobalModel:
|
||||
assert skip_reason == "Provider 未实现此模型"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_provider_with_matching_model_should_return_true(self):
|
||||
async def test_provider_with_matching_model_should_return_true(self) -> None:
|
||||
"""Provider 配置了目标模型时应该返回 True"""
|
||||
scheduler = CacheAwareScheduler()
|
||||
|
||||
@@ -136,7 +136,7 @@ class TestCheckModelSupportForGlobalModel:
|
||||
|
||||
with patch("sqlalchemy.inspect", return_value=mock_inspect):
|
||||
is_supported, skip_reason, caps, provider_model_names = (
|
||||
await scheduler._check_model_support_for_global_model(
|
||||
await scheduler._candidate_builder._check_model_support_for_global_model(
|
||||
db=db,
|
||||
provider=provider,
|
||||
global_model=global_model,
|
||||
@@ -150,7 +150,7 @@ class TestCheckModelSupportForGlobalModel:
|
||||
assert provider_model_names == {"claude-3-haiku-20240307"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_provider_with_inactive_model_should_return_false(self):
|
||||
async def test_provider_with_inactive_model_should_return_false(self) -> None:
|
||||
"""Provider 的模型未激活时应该返回 False"""
|
||||
scheduler = CacheAwareScheduler()
|
||||
|
||||
@@ -179,7 +179,7 @@ class TestCheckModelSupportForGlobalModel:
|
||||
|
||||
with patch("sqlalchemy.inspect", return_value=mock_inspect):
|
||||
is_supported, skip_reason, caps, provider_model_names = (
|
||||
await scheduler._check_model_support_for_global_model(
|
||||
await scheduler._candidate_builder._check_model_support_for_global_model(
|
||||
db=db,
|
||||
provider=provider,
|
||||
global_model=global_model,
|
||||
|
||||
@@ -20,14 +20,14 @@ def _make_key(
|
||||
|
||||
|
||||
@patch(
|
||||
"src.services.cache.aware_scheduler.health_monitor.get_circuit_breaker_status",
|
||||
"src.services.cache._candidate_builder.health_monitor.get_circuit_breaker_status",
|
||||
return_value=(True, None),
|
||||
)
|
||||
def test_kiro_quota_remaining_zero_skips(_mock_cb: MagicMock) -> None:
|
||||
scheduler = CacheAwareScheduler()
|
||||
key = _make_key(upstream_metadata={"kiro": {"remaining": 0.0}})
|
||||
|
||||
ok, reason, _mapped = scheduler._check_key_availability(
|
||||
ok, reason, _mapped = scheduler._candidate_builder._check_key_availability(
|
||||
key,
|
||||
api_format="openai:chat",
|
||||
model_name="any-model",
|
||||
@@ -39,14 +39,14 @@ def test_kiro_quota_remaining_zero_skips(_mock_cb: MagicMock) -> None:
|
||||
|
||||
|
||||
@patch(
|
||||
"src.services.cache.aware_scheduler.health_monitor.get_circuit_breaker_status",
|
||||
"src.services.cache._candidate_builder.health_monitor.get_circuit_breaker_status",
|
||||
return_value=(True, None),
|
||||
)
|
||||
def test_kiro_quota_remaining_positive_allows(_mock_cb: MagicMock) -> None:
|
||||
scheduler = CacheAwareScheduler()
|
||||
key = _make_key(upstream_metadata={"kiro": {"remaining": 1.0}})
|
||||
|
||||
ok, reason, _mapped = scheduler._check_key_availability(
|
||||
ok, reason, _mapped = scheduler._candidate_builder._check_key_availability(
|
||||
key,
|
||||
api_format="openai:chat",
|
||||
model_name="any-model",
|
||||
@@ -58,7 +58,7 @@ def test_kiro_quota_remaining_positive_allows(_mock_cb: MagicMock) -> None:
|
||||
|
||||
|
||||
@patch(
|
||||
"src.services.cache.aware_scheduler.health_monitor.get_circuit_breaker_status",
|
||||
"src.services.cache._candidate_builder.health_monitor.get_circuit_breaker_status",
|
||||
return_value=(True, None),
|
||||
)
|
||||
def test_codex_weekly_quota_exhausted_skips(_mock_cb: MagicMock) -> None:
|
||||
@@ -73,7 +73,7 @@ def test_codex_weekly_quota_exhausted_skips(_mock_cb: MagicMock) -> None:
|
||||
}
|
||||
)
|
||||
|
||||
ok, reason, _mapped = scheduler._check_key_availability(
|
||||
ok, reason, _mapped = scheduler._candidate_builder._check_key_availability(
|
||||
key,
|
||||
api_format="openai:cli",
|
||||
model_name="any-model",
|
||||
@@ -85,7 +85,7 @@ def test_codex_weekly_quota_exhausted_skips(_mock_cb: MagicMock) -> None:
|
||||
|
||||
|
||||
@patch(
|
||||
"src.services.cache.aware_scheduler.health_monitor.get_circuit_breaker_status",
|
||||
"src.services.cache._candidate_builder.health_monitor.get_circuit_breaker_status",
|
||||
return_value=(True, None),
|
||||
)
|
||||
def test_codex_5h_quota_exhausted_skips(_mock_cb: MagicMock) -> None:
|
||||
@@ -99,7 +99,7 @@ def test_codex_5h_quota_exhausted_skips(_mock_cb: MagicMock) -> None:
|
||||
}
|
||||
)
|
||||
|
||||
ok, reason, _mapped = scheduler._check_key_availability(
|
||||
ok, reason, _mapped = scheduler._candidate_builder._check_key_availability(
|
||||
key,
|
||||
api_format="openai:cli",
|
||||
model_name="any-model",
|
||||
@@ -111,7 +111,7 @@ def test_codex_5h_quota_exhausted_skips(_mock_cb: MagicMock) -> None:
|
||||
|
||||
|
||||
@patch(
|
||||
"src.services.cache.aware_scheduler.health_monitor.get_circuit_breaker_status",
|
||||
"src.services.cache._candidate_builder.health_monitor.get_circuit_breaker_status",
|
||||
return_value=(True, None),
|
||||
)
|
||||
def test_codex_ignores_code_review_quota(_mock_cb: MagicMock) -> None:
|
||||
@@ -126,7 +126,7 @@ def test_codex_ignores_code_review_quota(_mock_cb: MagicMock) -> None:
|
||||
}
|
||||
)
|
||||
|
||||
ok, reason, _mapped = scheduler._check_key_availability(
|
||||
ok, reason, _mapped = scheduler._candidate_builder._check_key_availability(
|
||||
key,
|
||||
api_format="openai:cli",
|
||||
model_name="any-model",
|
||||
@@ -138,7 +138,7 @@ def test_codex_ignores_code_review_quota(_mock_cb: MagicMock) -> None:
|
||||
|
||||
|
||||
@patch(
|
||||
"src.services.cache.aware_scheduler.health_monitor.get_circuit_breaker_status",
|
||||
"src.services.cache._candidate_builder.health_monitor.get_circuit_breaker_status",
|
||||
return_value=(True, None),
|
||||
)
|
||||
def test_antigravity_model_quota_exhausted_skips(_mock_cb: MagicMock) -> None:
|
||||
@@ -154,7 +154,7 @@ def test_antigravity_model_quota_exhausted_skips(_mock_cb: MagicMock) -> None:
|
||||
},
|
||||
)
|
||||
|
||||
ok, reason, _mapped = scheduler._check_key_availability(
|
||||
ok, reason, _mapped = scheduler._candidate_builder._check_key_availability(
|
||||
key,
|
||||
api_format="gemini:chat",
|
||||
model_name="ag-model",
|
||||
@@ -166,7 +166,7 @@ def test_antigravity_model_quota_exhausted_skips(_mock_cb: MagicMock) -> None:
|
||||
|
||||
|
||||
@patch(
|
||||
"src.services.cache.aware_scheduler.health_monitor.get_circuit_breaker_status",
|
||||
"src.services.cache._candidate_builder.health_monitor.get_circuit_breaker_status",
|
||||
return_value=(True, None),
|
||||
)
|
||||
def test_antigravity_other_model_not_exhausted_allows(_mock_cb: MagicMock) -> None:
|
||||
@@ -182,7 +182,7 @@ def test_antigravity_other_model_not_exhausted_allows(_mock_cb: MagicMock) -> No
|
||||
},
|
||||
)
|
||||
|
||||
ok, reason, _mapped = scheduler._check_key_availability(
|
||||
ok, reason, _mapped = scheduler._candidate_builder._check_key_availability(
|
||||
key,
|
||||
api_format="gemini:chat",
|
||||
model_name="other",
|
||||
@@ -194,7 +194,7 @@ def test_antigravity_other_model_not_exhausted_allows(_mock_cb: MagicMock) -> No
|
||||
|
||||
|
||||
@patch(
|
||||
"src.services.cache.aware_scheduler.health_monitor.get_circuit_breaker_status",
|
||||
"src.services.cache._candidate_builder.health_monitor.get_circuit_breaker_status",
|
||||
return_value=(True, None),
|
||||
)
|
||||
def test_antigravity_quota_uses_mapping_matched_model(_mock_cb: MagicMock) -> None:
|
||||
@@ -212,7 +212,7 @@ def test_antigravity_quota_uses_mapping_matched_model(_mock_cb: MagicMock) -> No
|
||||
allowed_models=["ag-model"],
|
||||
)
|
||||
|
||||
ok, reason, mapped = scheduler._check_key_availability(
|
||||
ok, reason, mapped = scheduler._candidate_builder._check_key_availability(
|
||||
key,
|
||||
api_format="gemini:chat",
|
||||
model_name="global-model",
|
||||
|
||||
@@ -38,8 +38,9 @@ async def test_build_candidates_allows_cross_format_when_endpoint_accepts_and_ov
|
||||
register_default_normalizers()
|
||||
|
||||
scheduler = CacheAwareScheduler()
|
||||
scheduler._check_model_support = AsyncMock(return_value=(True, None, None, {"m"})) # type: ignore[method-assign]
|
||||
scheduler._check_key_availability = MagicMock(return_value=(True, None, None)) # type: ignore[method-assign]
|
||||
builder = scheduler._candidate_builder
|
||||
builder._check_model_support = AsyncMock(return_value=(True, None, None, {"m"})) # type: ignore[method-assign]
|
||||
builder._check_key_availability = MagicMock(return_value=(True, None, None)) # type: ignore[method-assign]
|
||||
|
||||
provider = MagicMock()
|
||||
provider.name = "p1"
|
||||
@@ -52,7 +53,7 @@ async def test_build_candidates_allows_cross_format_when_endpoint_accepts_and_ov
|
||||
]
|
||||
provider.api_keys = [_mock_key("k1", ["openai:chat"])]
|
||||
|
||||
candidates = await scheduler._build_candidates(
|
||||
candidates = await builder._build_candidates(
|
||||
db=MagicMock(),
|
||||
providers=[provider],
|
||||
client_format="claude:chat",
|
||||
@@ -75,8 +76,9 @@ async def test_build_candidates_allows_cross_format_when_global_off_but_endpoint
|
||||
register_default_normalizers()
|
||||
|
||||
scheduler = CacheAwareScheduler()
|
||||
scheduler._check_model_support = AsyncMock(return_value=(True, None, None, {"m"})) # type: ignore[method-assign]
|
||||
scheduler._check_key_availability = MagicMock(return_value=(True, None, None)) # type: ignore[method-assign]
|
||||
builder = scheduler._candidate_builder
|
||||
builder._check_model_support = AsyncMock(return_value=(True, None, None, {"m"})) # type: ignore[method-assign]
|
||||
builder._check_key_availability = MagicMock(return_value=(True, None, None)) # type: ignore[method-assign]
|
||||
|
||||
provider = MagicMock()
|
||||
provider.name = "p1"
|
||||
@@ -89,7 +91,7 @@ async def test_build_candidates_allows_cross_format_when_global_off_but_endpoint
|
||||
]
|
||||
provider.api_keys = [_mock_key("k1", ["openai:chat"])]
|
||||
|
||||
candidates = await scheduler._build_candidates(
|
||||
candidates = await builder._build_candidates(
|
||||
db=MagicMock(),
|
||||
providers=[provider],
|
||||
client_format="claude:chat",
|
||||
@@ -114,8 +116,9 @@ async def test_build_candidates_blocks_cross_format_when_global_off_and_endpoint
|
||||
register_default_normalizers()
|
||||
|
||||
scheduler = CacheAwareScheduler()
|
||||
scheduler._check_model_support = AsyncMock(return_value=(True, None, None, {"m"})) # type: ignore[method-assign]
|
||||
scheduler._check_key_availability = MagicMock(return_value=(True, None, None)) # type: ignore[method-assign]
|
||||
builder = scheduler._candidate_builder
|
||||
builder._check_model_support = AsyncMock(return_value=(True, None, None, {"m"})) # type: ignore[method-assign]
|
||||
builder._check_key_availability = MagicMock(return_value=(True, None, None)) # type: ignore[method-assign]
|
||||
|
||||
provider = MagicMock()
|
||||
provider.name = "p1"
|
||||
@@ -123,7 +126,7 @@ async def test_build_candidates_blocks_cross_format_when_global_off_and_endpoint
|
||||
provider.endpoints = [_mock_endpoint("openai:chat", None)] # 端点未配置格式接受策略
|
||||
provider.api_keys = [_mock_key("k1", ["openai:chat"])]
|
||||
|
||||
candidates = await scheduler._build_candidates(
|
||||
candidates = await builder._build_candidates(
|
||||
db=MagicMock(),
|
||||
providers=[provider],
|
||||
client_format="claude:chat",
|
||||
@@ -141,8 +144,9 @@ async def test_build_candidates_includes_cross_format_when_enabled() -> None:
|
||||
register_default_normalizers()
|
||||
|
||||
scheduler = CacheAwareScheduler()
|
||||
scheduler._check_model_support = AsyncMock(return_value=(True, None, None, {"m"})) # type: ignore[method-assign]
|
||||
scheduler._check_key_availability = MagicMock(return_value=(True, None, None)) # type: ignore[method-assign]
|
||||
builder = scheduler._candidate_builder
|
||||
builder._check_model_support = AsyncMock(return_value=(True, None, None, {"m"})) # type: ignore[method-assign]
|
||||
builder._check_key_availability = MagicMock(return_value=(True, None, None)) # type: ignore[method-assign]
|
||||
|
||||
provider = MagicMock()
|
||||
provider.name = "p1"
|
||||
@@ -153,7 +157,7 @@ async def test_build_candidates_includes_cross_format_when_enabled() -> None:
|
||||
]
|
||||
provider.api_keys = [_mock_key("k1", ["openai:chat"])]
|
||||
|
||||
candidates = await scheduler._build_candidates(
|
||||
candidates = await builder._build_candidates(
|
||||
db=MagicMock(),
|
||||
providers=[provider],
|
||||
client_format="claude:chat",
|
||||
@@ -172,8 +176,9 @@ async def test_exact_matches_rank_before_convertible() -> None:
|
||||
register_default_normalizers()
|
||||
|
||||
scheduler = CacheAwareScheduler()
|
||||
scheduler._check_model_support = AsyncMock(return_value=(True, None, None, {"m"})) # type: ignore[method-assign]
|
||||
scheduler._check_key_availability = MagicMock(return_value=(True, None, None)) # type: ignore[method-assign]
|
||||
builder = scheduler._candidate_builder
|
||||
builder._check_model_support = AsyncMock(return_value=(True, None, None, {"m"})) # type: ignore[method-assign]
|
||||
builder._check_key_availability = MagicMock(return_value=(True, None, None)) # type: ignore[method-assign]
|
||||
|
||||
provider = MagicMock()
|
||||
provider.name = "p1"
|
||||
@@ -191,7 +196,7 @@ async def test_exact_matches_rank_before_convertible() -> None:
|
||||
_mock_key("k_claude", ["claude:chat"]),
|
||||
]
|
||||
|
||||
candidates = await scheduler._build_candidates(
|
||||
candidates = await builder._build_candidates(
|
||||
db=MagicMock(),
|
||||
providers=[provider],
|
||||
client_format="claude:chat",
|
||||
|
||||
Reference in New Issue
Block a user