Files
Aether/tests/services/test_task_pool_ops.py
fawney19 239238fe47 refactor(task): 拆分 TaskService 并重构任务生命周期
- 将 task 公共协议/上下文/异常/schema 下沉到 core,并迁移 polling 目录
- 新增 execute/submit/video 子模块,拆分同步执行、异步提交流程、错误处理与视频任务操作
- 收敛 TaskService 为门面编排,内部委派到 SyncTaskExecutionService、AsyncTaskSubmitService、VideoTaskOperationsService
- 重构 main 生命周期管理:引入 LifecycleState,拆分启动与关闭流程
- 删除未使用的 TaskExecuteFacadeService 与 TaskSubmitFacadeService

Closes #201

Co-authored-by: AAEE86 <ppk0227@hotmail.com>
2026-03-07 15:33:29 +08:00

119 lines
3.7 KiB
Python

from __future__ import annotations
from types import SimpleNamespace
from typing import Any, cast
import pytest
from src.models.database import Provider, ProviderAPIKey, ProviderEndpoint
from src.services.scheduling.schemas import PoolCandidate, ProviderCandidate
from src.services.task.execute.pool import TaskPoolOperationsService
def _provider(provider_id: str) -> Provider:
return cast(Provider, SimpleNamespace(id=provider_id))
def _endpoint(endpoint_id: str) -> ProviderEndpoint:
return cast(ProviderEndpoint, SimpleNamespace(id=endpoint_id))
def _key(key_id: str) -> ProviderAPIKey:
return cast(ProviderAPIKey, SimpleNamespace(id=key_id))
def test_extract_session_uuid_returns_none_for_non_dict_request_body() -> None:
svc = TaskPoolOperationsService()
assert svc.extract_session_uuid("openai", None) is None
assert svc.extract_session_uuid("openai", cast(Any, "not-dict")) is None
def test_extract_session_uuid_uses_pool_hook(monkeypatch: pytest.MonkeyPatch) -> None:
from src.services.provider.pool import hooks
hook = SimpleNamespace(extract_session_uuid=lambda body: f"sid:{body.get('session')}")
def _get_pool_hook(_provider_type: str) -> Any:
return hook
monkeypatch.setattr(hooks, "get_pool_hook", _get_pool_hook)
svc = TaskPoolOperationsService()
session_id = svc.extract_session_uuid("claude_code", {"session": "abc"})
assert session_id == "sid:abc"
def test_expand_pool_candidates_for_async_submit_keeps_non_pool_candidate() -> None:
provider = _provider("p1")
endpoint = _endpoint("e1")
key = _key("k1")
candidate = ProviderCandidate(provider=provider, endpoint=endpoint, key=key)
svc = TaskPoolOperationsService()
expanded = svc.expand_pool_candidates_for_async_submit([candidate])
assert len(expanded) == 1
assert expanded[0] is candidate
def test_expand_pool_candidates_for_async_submit_expands_pool_keys() -> None:
provider = _provider("p1")
endpoint = _endpoint("e1")
key = _key("k0")
pool_key_1 = cast(
ProviderAPIKey,
SimpleNamespace(
id="k1",
_pool_skipped=False,
_pool_mapping_matched_model="mapped-model",
_pool_extra_data={"source": "warm"},
),
)
pool_key_2 = cast(
ProviderAPIKey,
SimpleNamespace(
id="k2",
_pool_skipped=True,
_pool_skip_reason="cooldown",
_pool_extra_data={"reason_code": "429"},
),
)
pool_candidate = PoolCandidate(
provider=provider,
endpoint=endpoint,
key=key,
is_cached=True,
is_skipped=False,
skip_reason=None,
mapping_matched_model="fallback-model",
needs_conversion=True,
provider_api_format="openai:chat",
output_limit=1024,
capability_miss_count=1,
pool_keys=[pool_key_1, pool_key_2],
)
svc = TaskPoolOperationsService()
expanded = svc.expand_pool_candidates_for_async_submit([pool_candidate])
assert len(expanded) == 2
first, second = expanded
assert first.key is pool_key_1
assert first.is_skipped is False
assert first.mapping_matched_model == "mapped-model"
first_extra = getattr(first, "_pool_extra_data")
assert first_extra["pool_group_id"] == "p1"
assert first_extra["pool_key_index"] == 0
assert first_extra["source"] == "warm"
assert second.key is pool_key_2
assert second.is_skipped is True
assert second.skip_reason == "cooldown"
assert second.mapping_matched_model == "fallback-model"
second_extra = getattr(second, "_pool_extra_data")
assert second_extra["pool_group_id"] == "p1"
assert second_extra["pool_key_index"] == 1
assert second_extra["reason_code"] == "429"