Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
use nomifun_cron::prompt::{
|
||||
SKILL_SUGGEST_FILENAME, build_existing_conversation_prompt,
|
||||
build_new_conversation_prompt_with_skill_suggest, build_new_conversation_with_skill_prompt,
|
||||
build_skill_suggest_prompt,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn build_new_conversation_prompt_with_skill_suggest_includes_follow_up_block() {
|
||||
let prompt = build_new_conversation_prompt_with_skill_suggest("Daily Report", "Every day at 9am", "Summarize it.");
|
||||
assert!(prompt.contains(&format!("create a file named \"{SKILL_SUGGEST_FILENAME}\"")));
|
||||
assert!(prompt.contains("short kebab-case name"));
|
||||
assert!(prompt.contains("If you think the task is too simple or one-off to benefit from a skill file"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_new_conversation_with_skill_prompt_matches_frontend_copy() {
|
||||
let prompt = build_new_conversation_with_skill_prompt("Daily Report", "Summarize it.");
|
||||
assert_eq!(
|
||||
prompt,
|
||||
"[Scheduled Task Context]\nTask: Daily Report\n\nThis is a scheduled task execution. A skill file with detailed instructions has been loaded\ninto your workspace. You MUST read and follow the skill instructions precisely.\n\nRules:\n1. Execute the task directly — do NOT ask clarifying questions.\n2. Follow the output format, tone, sources, and steps defined in the skill.\n3. If the task requires external data (news, weather, etc.), search for the latest information.\n[/Scheduled Task Context]\n\nSummarize it."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_existing_conversation_prompt_matches_frontend_copy() {
|
||||
let prompt = build_existing_conversation_prompt("Daily Report", "Every day at 9am", "Summarize it.");
|
||||
assert_eq!(
|
||||
prompt,
|
||||
"[Scheduled Task Execution]\nTask: Daily Report\nSchedule: Every day at 9am\n\nThis message is NOT a conversation from the user — it is a scheduled task triggered automatically.\nThe text below is a TASK INSTRUCTION that you must execute, not something the user is saying to you.\n\nRules:\n1. Treat the instruction as a command to perform, not as a chat message to respond to.\n2. Execute it directly — do NOT ask clarifying questions.\n3. If the task requires external data (news, weather, etc.), search for the latest information.\n\nTask instruction:\nSummarize it."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_skill_suggest_prompt_matches_frontend_copy() {
|
||||
let prompt = build_skill_suggest_prompt("Daily Report");
|
||||
assert!(prompt.starts_with("The task \"Daily Report\" is a recurring scheduled task. Based on what you just did,"));
|
||||
assert!(prompt.contains("```markdown"));
|
||||
assert!(prompt.contains("Use concrete details from this execution, not placeholders."));
|
||||
assert!(
|
||||
prompt.ends_with(
|
||||
"If you think the task is too simple or one-off to benefit from a skill file, you can skip this."
|
||||
)
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use nomifun_cron::skill_file::{
|
||||
ParsedSkillContent, build_skill_content, content_hash, cron_skill_dir, cron_skill_file_path, parse_skill_content,
|
||||
read_skill_content, validate_skill_content, write_raw_skill_file, write_skill_file,
|
||||
};
|
||||
|
||||
fn unique_temp_dir(label: &str) -> std::path::PathBuf {
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_nanos();
|
||||
std::env::temp_dir().join(format!("nomifun-cron-{label}-{}-{nanos}", std::process::id()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_skill_content_matches_frontend_shape() {
|
||||
let content = build_skill_content(
|
||||
"Daily Report",
|
||||
"Line 1\nLine 2\r\nLine 3",
|
||||
"Run report",
|
||||
Some("Every day at 9am"),
|
||||
);
|
||||
|
||||
assert!(content.contains("name: Daily Report"));
|
||||
assert!(content.contains("description: Line 1 Line 2 Line 3"));
|
||||
assert!(content.contains("This is a scheduled task: **Daily Report**"));
|
||||
assert!(content.contains("Schedule: Every day at 9am"));
|
||||
assert!(content.contains("## Instructions"));
|
||||
assert!(content.ends_with("Run report"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_content_roundtrips_built_files() {
|
||||
let built = build_skill_content("My Job", "My Description", "First\n\nSecond", None);
|
||||
let parsed = parse_skill_content(&built).unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
ParsedSkillContent {
|
||||
name: "My Job".into(),
|
||||
description: "My Description".into(),
|
||||
body: "First\n\nSecond".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_content_skips_blank_lines_after_frontmatter() {
|
||||
let parsed = parse_skill_content("---\nname: Test\ndescription: Desc\n---\n\n\nPrompt").unwrap();
|
||||
assert_eq!(parsed.body, "Prompt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_skill_content_handles_empty_body() {
|
||||
let parsed = parse_skill_content("---\nname: Test\ndescription: Desc\n---\n\n").unwrap();
|
||||
assert_eq!(parsed.body, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_skill_content_rejects_placeholders() {
|
||||
let err =
|
||||
validate_skill_content("---\nname: skill-name\ndescription: Real description\n---\n\nReal body").unwrap_err();
|
||||
assert!(err.to_string().contains("template placeholder"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn content_hash_normalizes_line_endings_and_edges() {
|
||||
let a = content_hash("---\nname: Test\ndescription: Desc\n---\n\nBody\n");
|
||||
let b = content_hash("---\r\nname: Test\r\ndescription: Desc\r\n---\r\n\r\nBody");
|
||||
let c = content_hash(" ---\nname: Test\ndescription: Desc\n---\n\nBody ");
|
||||
assert_eq!(a, b);
|
||||
assert_eq!(a, c);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_read_and_resolve_skill_file_paths() {
|
||||
let base = unique_temp_dir("write-read");
|
||||
std::fs::create_dir_all(&base).unwrap();
|
||||
|
||||
let file_path = write_skill_file(
|
||||
&base,
|
||||
"job-123",
|
||||
"Daily Report",
|
||||
"Generate daily report",
|
||||
"Run report",
|
||||
Some("Every day at 9am"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
cron_skill_dir(&base, "job-123").unwrap(),
|
||||
base.join("cron").join("skills").join("cron-job-123")
|
||||
);
|
||||
assert_eq!(file_path, cron_skill_file_path(&base, "job-123").unwrap());
|
||||
|
||||
let raw = read_skill_content(&base, "job-123").await.unwrap().unwrap();
|
||||
let parsed = parse_skill_content(&raw).unwrap();
|
||||
assert_eq!(parsed.name, "Daily Report");
|
||||
assert_eq!(parsed.description, "Generate daily report");
|
||||
assert_eq!(parsed.body, "Run report");
|
||||
|
||||
std::fs::remove_dir_all(&base).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_raw_skill_file_validates_before_writing() {
|
||||
let base = unique_temp_dir("write-raw");
|
||||
std::fs::create_dir_all(&base).unwrap();
|
||||
|
||||
let err = write_raw_skill_file(&base, "job-456", "not valid").await.unwrap_err();
|
||||
assert!(err.to_string().contains("skill file must start with YAML frontmatter"));
|
||||
assert!(read_skill_content(&base, "job-456").await.unwrap().is_none());
|
||||
|
||||
std::fs::remove_dir_all(&base).unwrap();
|
||||
}
|
||||
Reference in New Issue
Block a user