Files
MyAiDesk/nomifun-tauri/crates/agent/nomi-agent/tests/plan_prompt_file_test.rs
T
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

263 lines
8.1 KiB
Rust

//! Integration tests for Plan Mode prompts and file management (task 3.4).
//!
//! Tests are numbered to match the test-plan.md identifiers (TC-3.4-*).
use std::fs;
use std::path::Path;
use nomi_agent::context::{SystemPromptCache, build_system_prompt};
use nomi_agent::plan::file::{plan_file_path, read_plan, write_plan};
use nomi_agent::plan::prompt::plan_mode_instructions;
// ---------------------------------------------------------------------------
// TC-3.4-01 plan_mode_instructions content
// ---------------------------------------------------------------------------
#[test]
fn tc_3_4_01_instructions_not_empty() {
let text = plan_mode_instructions();
assert!(!text.is_empty(), "instructions should not be empty");
}
#[test]
fn tc_3_4_01_instructions_guide_code_reading() {
let text = plan_mode_instructions();
assert!(
text.contains("Read") && text.contains("Grep") && text.contains("Glob"),
"instructions should reference read-only tools"
);
}
#[test]
fn tc_3_4_01_instructions_guide_plan_creation() {
let text = plan_mode_instructions();
// Should mention planning/design phases
assert!(
text.contains("plan") || text.contains("Plan"),
"instructions should guide plan creation"
);
}
#[test]
fn tc_3_4_01_instructions_mention_exit_tool() {
let text = plan_mode_instructions();
assert!(
text.contains("ExitPlanMode"),
"instructions should mention ExitPlanMode tool"
);
}
#[test]
fn tc_3_4_01_instructions_forbid_writes() {
let text = plan_mode_instructions();
assert!(
text.contains("MUST NOT") || text.contains("Forbidden"),
"instructions should forbid write operations"
);
}
// ---------------------------------------------------------------------------
// TC-3.4-03 System prompt with plan mode active
// ---------------------------------------------------------------------------
#[test]
fn tc_3_4_03_system_prompt_includes_plan_instructions_when_active() {
let result = build_system_prompt(
&mut SystemPromptCache::new(),
None,
"/tmp",
"test-model",
&[],
None,
None,
true,
false,
false,
);
// Should contain plan mode instructions
assert!(
result.contains("Plan Mode"),
"active plan mode should inject plan mode instructions"
);
assert!(
result.contains("ExitPlanMode"),
"plan mode instructions should mention ExitPlanMode"
);
assert!(
result.contains("MUST NOT"),
"plan mode instructions should contain restrictions"
);
}
// ---------------------------------------------------------------------------
// TC-3.4-04 System prompt without plan mode
// ---------------------------------------------------------------------------
#[test]
fn tc_3_4_04_system_prompt_excludes_plan_instructions_when_inactive() {
let result = build_system_prompt(
&mut SystemPromptCache::new(),
None,
"/tmp",
"test-model",
&[],
None,
None,
false,
false,
false,
);
// Should NOT contain plan mode instructions
assert!(
!result.contains("# Plan Mode"),
"inactive plan mode should not inject plan mode heading"
);
}
// ---------------------------------------------------------------------------
// TC-3.4-05 Plan file write
// ---------------------------------------------------------------------------
#[test]
fn tc_3_4_05_write_plan_creates_file_and_parents() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("deep").join("nested").join("plan.md");
write_plan(&path, "# My Plan\n\n## Steps\n1. Do thing").unwrap();
assert!(path.exists(), "plan file should be created");
let content = fs::read_to_string(&path).unwrap();
assert_eq!(content, "# My Plan\n\n## Steps\n1. Do thing");
}
// ---------------------------------------------------------------------------
// TC-3.4-06 Plan file read
// ---------------------------------------------------------------------------
#[test]
fn tc_3_4_06_read_plan_returns_content() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("plan.md");
fs::write(&path, "# My Plan\nStep 1").unwrap();
let result = read_plan(&path).unwrap();
assert_eq!(result, Some("# My Plan\nStep 1".to_string()));
}
// ---------------------------------------------------------------------------
// TC-3.4-07 Plan file read when not exists
// ---------------------------------------------------------------------------
#[test]
fn tc_3_4_07_read_plan_nonexistent_returns_none() {
let result = read_plan(Path::new("/nonexistent/path/plan.md")).unwrap();
assert_eq!(result, None, "reading nonexistent plan should return None");
}
// ---------------------------------------------------------------------------
// TC-3.4-08 Plan file path generation
// ---------------------------------------------------------------------------
#[test]
fn tc_3_4_08_plan_file_path_format() {
let path = plan_file_path(Path::new("/tmp/plans"), "session-abc");
assert_eq!(
path,
std::path::PathBuf::from("/tmp/plans/session-abc.md"),
"plan file path should be {{dir}}/{{session_id}}.md"
);
}
// ---------------------------------------------------------------------------
// TC-3.4-09 No bb brand identifiers in plan mode instructions
// ---------------------------------------------------------------------------
#[test]
fn tc_3_4_09_no_bb_brand_in_instructions() {
let text = plan_mode_instructions();
assert!(
!text.contains("Claude"),
"instructions should not contain Claude brand"
);
assert!(
!text.contains("claude"),
"instructions should not contain lowercase claude"
);
assert!(
!text.contains("~/.claude"),
"instructions should not contain bb config path"
);
}
// ---------------------------------------------------------------------------
// Additional: write-then-read roundtrip
// ---------------------------------------------------------------------------
#[test]
fn write_then_read_roundtrip() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("plans");
let path = plan_file_path(&dir, "test-session");
let content = "# Implementation Plan\n\n## Context\nRefactor auth module\n\n## Files\n- src/auth.rs\n- src/middleware.rs";
write_plan(&path, content).unwrap();
let result = read_plan(&path).unwrap();
assert_eq!(result, Some(content.to_string()));
}
// ---------------------------------------------------------------------------
// Additional: plan mode instructions appear in correct position in system prompt
// ---------------------------------------------------------------------------
#[test]
fn plan_instructions_appear_after_memory_before_skills() {
let tmp = tempfile::TempDir::new().unwrap();
let mem_dir = tmp.path().join("memory");
std::fs::create_dir_all(&mem_dir).unwrap();
std::fs::write(mem_dir.join("MEMORY.md"), "- [A](a.md) \u{2014} test\n").unwrap();
let result = build_system_prompt(
&mut SystemPromptCache::new(),
None,
"/tmp",
"test-model",
&[],
None,
Some(&mem_dir),
true,
false,
false,
);
let memory_pos = result
.find("auto memory")
.expect("memory section should be present");
let plan_pos = result
.find("# Plan Mode")
.expect("plan mode instructions should be present");
assert!(
memory_pos < plan_pos,
"memory should appear before plan mode instructions"
);
}
// ---------------------------------------------------------------------------
// Additional: write_plan overwrites existing
// ---------------------------------------------------------------------------
#[test]
fn write_plan_overwrites_existing_content() {
let tmp = tempfile::TempDir::new().unwrap();
let path = tmp.path().join("plan.md");
write_plan(&path, "version 1").unwrap();
write_plan(&path, "version 2").unwrap();
let result = read_plan(&path).unwrap();
assert_eq!(result, Some("version 2".to_string()));
}