f7a720204a
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
276 lines
9.0 KiB
Rust
276 lines
9.0 KiB
Rust
//! Integration tests for Plan Mode tools (task 3.3).
|
|
//!
|
|
//! Tests are numbered to match the test-plan.md identifiers (TC-3.3-*).
|
|
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
use serde_json::json;
|
|
|
|
use nomi_agent::plan::state::PlanState;
|
|
use nomi_agent::plan::tools::{EnterPlanModeTool, ExitPlanModeTool};
|
|
use nomi_protocol::events::ToolCategory;
|
|
use nomi_tools::Tool;
|
|
use nomi_types::skill_types::PlanModeTransition;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TC-3.3-01 PlanState initial state
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn tc_3_3_01_plan_state_default_is_inactive() {
|
|
let state = PlanState::default();
|
|
assert!(!state.is_active);
|
|
}
|
|
|
|
#[test]
|
|
fn tc_3_3_01_plan_state_default_allow_list_empty() {
|
|
let state = PlanState::default();
|
|
assert!(state.pre_plan_allow_list.is_empty());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TC-3.3-02 EnterPlanMode normal execution
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn tc_3_3_02_enter_plan_mode_succeeds_when_not_active() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = EnterPlanModeTool::new(flag);
|
|
|
|
let result = tool.execute(json!({})).await;
|
|
|
|
assert!(!result.is_error, "should succeed when not in plan mode");
|
|
assert!(
|
|
result.content.contains("plan mode"),
|
|
"confirmation message should mention plan mode"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TC-3.3-03 EnterPlanMode duplicate entry rejected
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn tc_3_3_03_enter_plan_mode_rejects_when_already_active() {
|
|
let flag = Arc::new(AtomicBool::new(true));
|
|
let tool = EnterPlanModeTool::new(flag);
|
|
|
|
let result = tool.execute(json!({})).await;
|
|
|
|
assert!(result.is_error, "should fail when already in plan mode");
|
|
assert!(
|
|
result.content.contains("Already in plan mode"),
|
|
"error message should indicate already in plan mode"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TC-3.3-04 ExitPlanMode normal execution
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn tc_3_3_04_exit_plan_mode_succeeds_when_active() {
|
|
let flag = Arc::new(AtomicBool::new(true));
|
|
let tool = ExitPlanModeTool::new(flag);
|
|
|
|
let result = tool.execute(json!({})).await;
|
|
|
|
assert!(!result.is_error, "should succeed when in plan mode");
|
|
assert!(
|
|
result.content.contains("Exited plan mode"),
|
|
"confirmation message should mention exiting"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TC-3.3-05 ExitPlanMode when not in plan mode
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn tc_3_3_05_exit_plan_mode_rejects_when_not_active() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = ExitPlanModeTool::new(flag);
|
|
|
|
let result = tool.execute(json!({})).await;
|
|
|
|
assert!(result.is_error, "should fail when not in plan mode");
|
|
assert!(
|
|
result.content.contains("Not in plan mode"),
|
|
"error message should indicate not in plan mode"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TC-3.3-06 EnterPlanMode context_modifier returns Enter
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn tc_3_3_06_enter_context_modifier_returns_enter_transition() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = EnterPlanModeTool::new(flag);
|
|
|
|
let modifier = tool.context_modifier_for(&json!({}));
|
|
|
|
assert!(modifier.is_some(), "should return a context modifier");
|
|
let cm = modifier.unwrap();
|
|
assert_eq!(
|
|
cm.plan_mode_transition,
|
|
Some(PlanModeTransition::Enter),
|
|
"transition should be Enter"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TC-3.3-07 ExitPlanMode context_modifier returns Exit
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn tc_3_3_07_exit_context_modifier_returns_exit_transition() {
|
|
let flag = Arc::new(AtomicBool::new(true));
|
|
let tool = ExitPlanModeTool::new(flag);
|
|
|
|
let modifier = tool.context_modifier_for(&json!({}));
|
|
|
|
assert!(modifier.is_some(), "should return a context modifier");
|
|
let cm = modifier.unwrap();
|
|
assert!(
|
|
matches!(
|
|
cm.plan_mode_transition,
|
|
Some(PlanModeTransition::Exit { .. })
|
|
),
|
|
"transition should be Exit variant"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TC-3.3-08 EnterPlanMode tool metadata
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn tc_3_3_08_enter_tool_name() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = EnterPlanModeTool::new(flag);
|
|
assert_eq!(tool.name(), "EnterPlanMode");
|
|
}
|
|
|
|
#[test]
|
|
fn tc_3_3_08_enter_tool_category_is_info() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = EnterPlanModeTool::new(flag);
|
|
assert!(matches!(tool.category(), ToolCategory::Info));
|
|
}
|
|
|
|
#[test]
|
|
fn tc_3_3_08_enter_tool_is_concurrency_safe() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = EnterPlanModeTool::new(flag);
|
|
assert!(tool.is_concurrency_safe(&json!({})));
|
|
}
|
|
|
|
#[test]
|
|
fn tc_3_3_08_enter_tool_schema_no_required_params() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = EnterPlanModeTool::new(flag);
|
|
let schema = tool.input_schema();
|
|
let required = schema["required"]
|
|
.as_array()
|
|
.expect("required should be an array");
|
|
assert!(required.is_empty(), "no required parameters expected");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TC-3.3-09 ExitPlanMode tool metadata
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn tc_3_3_09_exit_tool_name() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = ExitPlanModeTool::new(flag);
|
|
assert_eq!(tool.name(), "ExitPlanMode");
|
|
}
|
|
|
|
#[test]
|
|
fn tc_3_3_09_exit_tool_category_is_info() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = ExitPlanModeTool::new(flag);
|
|
assert!(matches!(tool.category(), ToolCategory::Info));
|
|
}
|
|
|
|
#[test]
|
|
fn tc_3_3_09_exit_tool_is_concurrency_safe() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = ExitPlanModeTool::new(flag);
|
|
assert!(tool.is_concurrency_safe(&json!({})));
|
|
}
|
|
|
|
#[test]
|
|
fn tc_3_3_09_exit_tool_schema_no_required_params() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let tool = ExitPlanModeTool::new(flag);
|
|
let schema = tool.input_schema();
|
|
let required = schema["required"]
|
|
.as_array()
|
|
.expect("required should be an array");
|
|
assert!(required.is_empty(), "no required parameters expected");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Additional: full enter-exit cycle with shared flag
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn enter_exit_cycle_with_shared_flag() {
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let enter = EnterPlanModeTool::new(Arc::clone(&flag));
|
|
let exit = ExitPlanModeTool::new(Arc::clone(&flag));
|
|
|
|
// Phase 1: enter should succeed
|
|
let r = enter.execute(json!({})).await;
|
|
assert!(!r.is_error);
|
|
|
|
// Simulate engine applying the transition
|
|
flag.store(true, Ordering::Release);
|
|
|
|
// Phase 2: double-enter should fail
|
|
let r = enter.execute(json!({})).await;
|
|
assert!(r.is_error);
|
|
|
|
// Phase 3: exit should succeed
|
|
let r = exit.execute(json!({})).await;
|
|
assert!(!r.is_error);
|
|
|
|
// Simulate engine applying the transition
|
|
flag.store(false, Ordering::Release);
|
|
|
|
// Phase 4: double-exit should fail
|
|
let r = exit.execute(json!({})).await;
|
|
assert!(r.is_error);
|
|
|
|
// Phase 5: re-enter should succeed
|
|
let r = enter.execute(json!({})).await;
|
|
assert!(!r.is_error);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Additional: context_modifier fields are default except plan_mode_transition
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn enter_context_modifier_other_fields_are_default() {
|
|
let tool = EnterPlanModeTool::new(Arc::new(AtomicBool::new(false)));
|
|
let cm = tool.context_modifier_for(&json!({})).unwrap();
|
|
assert!(cm.model.is_none());
|
|
assert!(cm.effort.is_none());
|
|
assert!(cm.allowed_tools.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn exit_context_modifier_other_fields_are_default() {
|
|
let tool = ExitPlanModeTool::new(Arc::new(AtomicBool::new(false)));
|
|
let cm = tool.context_modifier_for(&json!({})).unwrap();
|
|
assert!(cm.model.is_none());
|
|
assert!(cm.effort.is_none());
|
|
assert!(cm.allowed_tools.is_empty());
|
|
}
|