修复: Wave5 审批子系统收尾(R7补 types.ts + R9⑪ failed_node + R8 Request 测试)

- R7补: types.ts event.type string→WorkflowEventType 字面量联合(11 变体 snake_case),静态拦截写错 type
- R9⑪: workflow.rs WorkflowFailed failed_node String::new()→状态机取首个 Failed/Cancelled 节点
- R8: human_node 加 request_is_emitted_to_bus 测试,验证 R6(Request 真发到 EventBus)
cargo/vue-tsc 0 err / df-nodes 15 test pass
This commit is contained in:
2026-06-14 16:40:43 +08:00
parent 0ec2b3fd56
commit 3e1f119003
3 changed files with 51 additions and 2 deletions

View File

@@ -195,6 +195,40 @@ mod tests {
assert_eq!(out.data["comment"], json!("好的"));
}
#[tokio::test]
async fn request_is_emitted_to_bus() {
// B-03b-R8: 验证 HumanNode 真发出 HumanApprovalRequest 到事件总线
// (R6 修复前 :41 send 缺 await → Request 未进 channel此测会超时失败)
let bus = EventBus::new();
let mut rx = bus.subscribe(); // subscribe 先于 execute send(broadcast 不回放)
let ctx = make_ctx(&bus, "exec-1", "node-h", json!({ "timeout_secs": 1 }));
let bus_clone = bus.clone();
let handle = tokio::spawn(async move { HumanNode.execute(ctx).await });
// execute send Request 后 rx 应收到
let received = tokio::time::timeout(Duration::from_millis(500), rx.recv()).await;
assert!(
received.is_ok(),
"应收到 HumanApprovalRequest(R6 Request 真发出),实际超时"
);
match received.unwrap().unwrap() {
WorkflowEvent::HumanApprovalRequest {
execution_id,
node_id,
title,
..
} => {
assert_eq!(execution_id, "exec-1");
assert_eq!(node_id, "node-h");
assert_eq!(title, "请确认");
}
other => panic!("期望 HumanApprovalRequest收到 {:?}", other),
}
// 发 Response 让 execute 正常返回(防 task 泄漏)
send_response(&bus_clone, "exec-1", "node-h", "同意", None).await;
let _ = handle.await;
}
#[tokio::test]
async fn mismatched_execution_id_filtered_then_timeout() {
// execution_id 不匹配Response 被过滤,短超时验证 → Err 超时

View File

@@ -132,11 +132,20 @@ pub async fn run_workflow(
state_registry.lock().await.remove(&exec_id);
// 执行失败时补发 WorkflowFailed执行器内部只发 NodeFailed
// failed_node 从状态机取首个失败/取消节点(R9⑪: 原 String::new() 空值)
if let Some(error) = error {
use df_core::types::NodeStatus;
let failed_node = executor
.state_machine()
.snapshot()
.into_iter()
.find(|(_, s)| matches!(s, NodeStatus::Failed | NodeStatus::Cancelled))
.map(|(id, _)| id)
.unwrap_or_default();
event_bus
.send(WorkflowEvent::WorkflowFailed {
error,
failed_node: String::new(),
failed_node,
})
.await;
}

View File

@@ -121,10 +121,16 @@ export interface WorkflowRecord {
}
/** 工作流事件载荷(后端 emit 的结构) */
/** 工作流事件类型字面量联合(对应后端 WorkflowEvent serde tag=type rename_all=snake_caseR7收窄防写错 type 字符串) */
export type WorkflowEventType =
| 'node_started' | 'node_progress' | 'node_output' | 'node_completed' | 'node_failed'
| 'workflow_paused' | 'workflow_completed' | 'workflow_failed'
| 'human_approval_request' | 'human_approval_response'
export interface WorkflowEventPayload {
execution_id: string
event: {
type: string
type: WorkflowEventType
[key: string]: unknown
}
}