"""``ConsciousnessNode.start_workflow_design`` fire workflow task 的提交逻辑。 历史上这里有一个常驻的 ``WorkflowRunningEngine`` actor 做中转,现已删除: workflow 是一次性、有头有尾的执行,直接以 ``asyncio.create_task`` 触发 ``run_workflow_task``。本测试保证 ConsciousnessNode 在工作流生成后正确 fire, 并通过 ``put_pending`` 推送 SSE 进度(节点端写 pending → API 端 SSE 读 pending)。 """ from __future__ import annotations import asyncio from unittest.mock import AsyncMock, MagicMock import pytest from kilostar.core.work.workflow import workflow_engine as engine_module from kilostar.core.work.workflow.workflow import KiloStarWorkflow from kilostar.core.work.workflow.model import WorkflowMetadata @pytest.fixture def consciousness_instance(): from kilostar.core.individual.consciousness_node.consciousness_node import ( ConsciousnessNode, ) from kilostar.utils.logger import get_logger obj = ConsciousnessNode.__new__(ConsciousnessNode) obj.logger = get_logger("consciousness_node") obj.agent = None return obj @pytest.mark.asyncio async def test_start_workflow_design_fires_run_workflow_task( consciousness_instance, fake_actors, monkeypatch ): """快乐路径:working 返回 ForWorkflowEngine,应 fire run_workflow_task 且推送 pending。""" from kilostar.core.individual.consciousness_node.template import ( ForWorkflowEngine, ) wf = KiloStarWorkflow( title="t", work_link=[], workflow_metadata=WorkflowMetadata(), ) consciousness_instance.working = AsyncMock( return_value=ForWorkflowEngine(workflow=wf, reasoning="r") ) postgres = MagicMock() postgres.get_all_worker_individual = AsyncMock(return_value=[]) postgres.update_workflow_status = AsyncMock() fake_actors.register("postgres_database", postgres) pending_writes: list[tuple[str, str]] = [] gwm = MagicMock() gwm.put_pending = AsyncMock( side_effect=lambda tid, msg: pending_writes.append((tid, msg)) ) fake_actors.register("global_workflow_manager", gwm) captured: dict = {} async def _fake_task(workflow_dict, trace_id): captured["workflow_dict"] = workflow_dict captured["trace_id"] = trace_id monkeypatch.setattr(engine_module, "run_workflow_task", _fake_task) await consciousness_instance.start_workflow_design("trace-123", "do something") # 让 create_task 调度的协程跑完 await asyncio.sleep(0) assert captured["trace_id"] == "trace-123" assert captured["workflow_dict"]["title"] == "t" # SSE 推送方向必须是 pending(put_pending) assert any("正在为您构建" in msg for _, msg in pending_writes) assert any("即将开始执行" in msg for _, msg in pending_writes) @pytest.mark.asyncio async def test_start_workflow_design_failed_path_marks_failed( consciousness_instance, fake_actors, monkeypatch ): """working 返回 None / 不匹配类型时应推送失败提示并把 workflow 状态置为 failed。""" consciousness_instance.working = AsyncMock(return_value=None) postgres = MagicMock() postgres.get_all_worker_individual = AsyncMock(return_value=[]) postgres.update_workflow_status = AsyncMock() fake_actors.register("postgres_database", postgres) pending_writes: list[tuple[str, str]] = [] gwm = MagicMock() gwm.put_pending = AsyncMock( side_effect=lambda tid, msg: pending_writes.append((tid, msg)) ) fake_actors.register("global_workflow_manager", gwm) fired: list = [] async def _fake_task(*a, **kw): fired.append((a, kw)) monkeypatch.setattr(engine_module, "run_workflow_task", _fake_task) await consciousness_instance.start_workflow_design("trace-x", "cmd") await asyncio.sleep(0) assert fired == [] # 没有 fire workflow task postgres.update_workflow_status.assert_awaited_with("trace-x", "failed") assert any("生成失败" in msg for _, msg in pending_writes)