mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
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>
This commit is contained in:
132
tests/services/test_sync_execute_state_transition.py
Normal file
132
tests/services/test_sync_execute_state_transition.py
Normal file
@@ -0,0 +1,132 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from src.services.candidate.policy import FailoverAction
|
||||
from src.services.task.execute.exception_classification import (
|
||||
CandidateErrorAction,
|
||||
classify_candidate_error_action,
|
||||
)
|
||||
from src.services.task.execute.state_transition import (
|
||||
SyncExecutionState,
|
||||
resolve_execution_error_transition,
|
||||
)
|
||||
|
||||
|
||||
def _make_candidate() -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
provider=SimpleNamespace(id="p1", name="provider-1"),
|
||||
endpoint=SimpleNamespace(id="e1"),
|
||||
key=SimpleNamespace(id="k1"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raw_action", "expected"),
|
||||
[
|
||||
("continue", CandidateErrorAction.RETRY_CURRENT),
|
||||
("break", CandidateErrorAction.NEXT_CANDIDATE),
|
||||
("raise", CandidateErrorAction.RAISE_ERROR),
|
||||
("unexpected", CandidateErrorAction.NEXT_CANDIDATE),
|
||||
(None, CandidateErrorAction.NEXT_CANDIDATE),
|
||||
],
|
||||
)
|
||||
def test_classify_candidate_error_action(
|
||||
raw_action: str | None, expected: CandidateErrorAction
|
||||
) -> None:
|
||||
assert classify_candidate_error_action(raw_action) == expected
|
||||
|
||||
|
||||
def test_resolve_execution_error_transition_retry_and_consume_rectify_flag() -> None:
|
||||
request_body_ref = {"_rectified_this_turn": True}
|
||||
state = SyncExecutionState(
|
||||
candidate_record_map={},
|
||||
request_body_ref=request_body_ref,
|
||||
)
|
||||
|
||||
transition = resolve_execution_error_transition(
|
||||
action=CandidateErrorAction.RETRY_CURRENT,
|
||||
state=state,
|
||||
max_retries_for_candidate=2,
|
||||
retry_index=1,
|
||||
)
|
||||
|
||||
assert transition.failover_action == FailoverAction.RETRY
|
||||
assert transition.max_retries == 3
|
||||
assert request_body_ref["_rectified_this_turn"] is False
|
||||
|
||||
|
||||
def test_resolve_execution_error_transition_next_candidate() -> None:
|
||||
state = SyncExecutionState(
|
||||
candidate_record_map={},
|
||||
request_body_ref={"_rectified_this_turn": True},
|
||||
)
|
||||
|
||||
transition = resolve_execution_error_transition(
|
||||
action=CandidateErrorAction.NEXT_CANDIDATE,
|
||||
state=state,
|
||||
max_retries_for_candidate=2,
|
||||
retry_index=0,
|
||||
)
|
||||
|
||||
assert transition.failover_action == FailoverAction.CONTINUE
|
||||
assert transition.max_retries is None
|
||||
assert state.request_body_ref == {"_rectified_this_turn": True}
|
||||
|
||||
|
||||
def test_sync_execution_state_resolve_candidate_record_id_fallback() -> None:
|
||||
state = SyncExecutionState(
|
||||
candidate_record_map={(2, 0): "r20"},
|
||||
request_body_ref=None,
|
||||
)
|
||||
|
||||
assert state.resolve_candidate_record_id(candidate_index=2, record_id=None) == "r20"
|
||||
assert state.resolve_candidate_record_id(candidate_index=2, record_id="r22") == "r22"
|
||||
|
||||
|
||||
def test_sync_execution_state_raise_classified_error_uses_last_error() -> None:
|
||||
err = ValueError("boom")
|
||||
candidate = _make_candidate()
|
||||
state = SyncExecutionState(
|
||||
candidate_record_map={},
|
||||
request_body_ref=None,
|
||||
last_error=err,
|
||||
last_candidate=candidate,
|
||||
)
|
||||
failure_ops = MagicMock()
|
||||
|
||||
with pytest.raises(ValueError, match="boom"):
|
||||
state.raise_classified_error(
|
||||
fallback_error=RuntimeError("fallback"),
|
||||
failure_ops=failure_ops,
|
||||
model_name="gpt-4.1",
|
||||
api_format="openai_chat",
|
||||
)
|
||||
|
||||
failure_ops.attach_metadata_to_error.assert_called_once_with(
|
||||
err,
|
||||
candidate,
|
||||
"gpt-4.1",
|
||||
"openai_chat",
|
||||
)
|
||||
|
||||
|
||||
def test_sync_execution_state_raise_classified_error_fallback_error() -> None:
|
||||
state = SyncExecutionState(
|
||||
candidate_record_map={},
|
||||
request_body_ref=None,
|
||||
)
|
||||
failure_ops = MagicMock()
|
||||
|
||||
with pytest.raises(RuntimeError, match="fallback"):
|
||||
state.raise_classified_error(
|
||||
fallback_error=RuntimeError("fallback"),
|
||||
failure_ops=failure_ops,
|
||||
model_name="gpt-4.1",
|
||||
api_format="openai_chat",
|
||||
)
|
||||
|
||||
failure_ops.attach_metadata_to_error.assert_not_called()
|
||||
Reference in New Issue
Block a user