diff --git a/server/src/http/mcp/story_tools.rs b/server/src/http/mcp/story_tools.rs index a4a78df6..2a16ed45 100644 --- a/server/src/http/mcp/story_tools.rs +++ b/server/src/http/mcp/story_tools.rs @@ -1521,6 +1521,85 @@ mod tests { ); } + // ── tool_update_story non-string front matter tests ─────────────────────── + + fn setup_story_for_update(dir: &std::path::Path, story_id: &str, content: &str) { + let current = dir.join(".huskies/work/2_current"); + fs::create_dir_all(¤t).unwrap(); + fs::write(current.join(format!("{story_id}.md")), content).unwrap(); + crate::db::ensure_content_store(); + crate::db::write_content(story_id, content); + } + + #[test] + fn tool_update_story_front_matter_json_bool_written_unquoted() { + let tmp = tempfile::tempdir().unwrap(); + setup_story_for_update( + tmp.path(), + "504_bool_test", + "---\nname: Bool Test\n---\n\nNo sections.\n", + ); + let ctx = test_ctx(tmp.path()); + + let result = tool_update_story( + &json!({"story_id": "504_bool_test", "front_matter": {"blocked": false}}), + &ctx, + ); + assert!(result.is_ok(), "Expected ok: {result:?}"); + + let content = crate::db::read_content("504_bool_test").unwrap(); + assert!(content.contains("blocked: false"), "bool should be unquoted: {content}"); + assert!(!content.contains("blocked: \"false\""), "bool must not be quoted: {content}"); + } + + #[test] + fn tool_update_story_front_matter_json_number_written_unquoted() { + let tmp = tempfile::tempdir().unwrap(); + setup_story_for_update( + tmp.path(), + "504_num_test", + "---\nname: Num Test\n---\n\nNo sections.\n", + ); + let ctx = test_ctx(tmp.path()); + + let result = tool_update_story( + &json!({"story_id": "504_num_test", "front_matter": {"retry_count": 3}}), + &ctx, + ); + assert!(result.is_ok(), "Expected ok: {result:?}"); + + let content = crate::db::read_content("504_num_test").unwrap(); + assert!(content.contains("retry_count: 3"), "number should be unquoted: {content}"); + assert!(!content.contains("retry_count: \"3\""), "number must not be quoted: {content}"); + } + + #[test] + fn tool_update_story_front_matter_json_array_written_as_yaml_sequence() { + let tmp = tempfile::tempdir().unwrap(); + setup_story_for_update( + tmp.path(), + "504_arr_test", + "---\nname: Array Test\n---\n\nNo sections.\n", + ); + let ctx = test_ctx(tmp.path()); + + let result = tool_update_story( + &json!({"story_id": "504_arr_test", "front_matter": {"depends_on": [490, 491]}}), + &ctx, + ); + assert!(result.is_ok(), "Expected ok: {result:?}"); + + let content = crate::db::read_content("504_arr_test").unwrap(); + // YAML inline sequences use spaces after commas + assert!(content.contains("depends_on: [490, 491]"), "array should be unquoted YAML: {content}"); + assert!(!content.contains("depends_on: \""), "array must not be quoted: {content}"); + + // The YAML must be parseable as a vec + let meta = crate::io::story_metadata::parse_front_matter(&content) + .expect("front matter should parse"); + assert_eq!(meta.depends_on, Some(vec![490, 491])); + } + #[test] fn tool_check_criterion_missing_story_id() { let tmp = tempfile::tempdir().unwrap();