From 71c2bbdc97d2368f46de5f2a2c54a182bad4caf3 Mon Sep 17 00:00:00 2001 From: th-kim0823 Date: Thu, 7 May 2026 18:14:07 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20test(kebab-mcp):=20ingest=5Ffile?= =?UTF-8?q?=20+=20ingest=5Fstdin=20integration=20(fb-31)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../kebab-mcp/tests/tools_call_ingest_file.rs | 53 +++++++++++ .../tests/tools_call_ingest_stdin.rs | 89 +++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 crates/kebab-mcp/tests/tools_call_ingest_file.rs create mode 100644 crates/kebab-mcp/tests/tools_call_ingest_stdin.rs diff --git a/crates/kebab-mcp/tests/tools_call_ingest_file.rs b/crates/kebab-mcp/tests/tools_call_ingest_file.rs new file mode 100644 index 0000000..e9eab6e --- /dev/null +++ b/crates/kebab-mcp/tests/tools_call_ingest_file.rs @@ -0,0 +1,53 @@ +//! Integration: tools/call name=ingest_file → ingest_report.v1. + +use std::fs; + +use kebab_config::Config; +use kebab_mcp::{KebabAppState, KebabHandler}; +use rmcp::model::RawContent; + +#[tokio::test] +async fn ingest_file_tool_returns_ingest_report_v1() { + let dir = tempfile::tempdir().unwrap(); + let workspace = dir.path().join("notes"); + let data = dir.path().join("data"); + fs::create_dir_all(&workspace).unwrap(); + fs::create_dir_all(&data).unwrap(); + + let mut cfg = Config::defaults(); + cfg.workspace.root = workspace.to_string_lossy().into_owned(); + cfg.storage.data_dir = data.to_string_lossy().into_owned(); + cfg.models.embedding.provider = "none".to_string(); + cfg.models.embedding.dimensions = 0; + + let src = dir.path().join("doc.md"); + fs::write(&src, "# Title\n\nbody.").unwrap(); + + let state = KebabAppState::new(cfg, None); + let handler = KebabHandler::new(state); + + let result = tokio::task::spawn_blocking({ + let state = handler.state().clone(); + let path = src.to_string_lossy().into_owned(); + move || { + kebab_mcp::tools::ingest_file::handle( + &state, + kebab_mcp::tools::ingest_file::IngestFileInput { path }, + ) + } + }) + .await + .unwrap(); + + assert!(!result.is_error.unwrap_or(false), "{result:?}"); + let text = match &result.content.first().unwrap().raw { + RawContent::Text(t) => &t.text, + other => panic!("expected text content, got {other:?}"), + }; + let v: serde_json::Value = serde_json::from_str(text).unwrap(); + assert_eq!( + v.get("schema_version").and_then(|s| s.as_str()), + Some("ingest_report.v1") + ); + assert_eq!(v.get("new").and_then(|n| n.as_u64()), Some(1)); +} diff --git a/crates/kebab-mcp/tests/tools_call_ingest_stdin.rs b/crates/kebab-mcp/tests/tools_call_ingest_stdin.rs new file mode 100644 index 0000000..45943d6 --- /dev/null +++ b/crates/kebab-mcp/tests/tools_call_ingest_stdin.rs @@ -0,0 +1,89 @@ +//! Integration: tools/call name=ingest_stdin → ingest_report.v1. +//! Frontmatter precheck path also covered. + +use std::fs; + +use kebab_config::Config; +use kebab_mcp::KebabAppState; +use rmcp::model::RawContent; + +fn fresh_state(dir: &std::path::Path) -> KebabAppState { + let workspace = dir.join("notes"); + let data = dir.join("data"); + fs::create_dir_all(&workspace).unwrap(); + fs::create_dir_all(&data).unwrap(); + + let mut cfg = Config::defaults(); + cfg.workspace.root = workspace.to_string_lossy().into_owned(); + cfg.storage.data_dir = data.to_string_lossy().into_owned(); + cfg.models.embedding.provider = "none".to_string(); + cfg.models.embedding.dimensions = 0; + KebabAppState::new(cfg, None) +} + +#[tokio::test] +async fn ingest_stdin_tool_returns_ingest_report_v1() { + let dir = tempfile::tempdir().unwrap(); + let state = fresh_state(dir.path()); + + let result = tokio::task::spawn_blocking({ + let state = state.clone(); + move || { + kebab_mcp::tools::ingest_stdin::handle( + &state, + kebab_mcp::tools::ingest_stdin::IngestStdinInput { + content: "## Body".to_string(), + title: "X".to_string(), + source_uri: Some("https://example.com/x".to_string()), + }, + ) + } + }) + .await + .unwrap(); + + assert!(!result.is_error.unwrap_or(false), "{result:?}"); + let text = match &result.content.first().unwrap().raw { + RawContent::Text(t) => &t.text, + other => panic!("expected text content, got {other:?}"), + }; + let v: serde_json::Value = serde_json::from_str(text).unwrap(); + assert_eq!( + v.get("schema_version").and_then(|s| s.as_str()), + Some("ingest_report.v1") + ); + assert_eq!(v.get("new").and_then(|n| n.as_u64()), Some(1)); +} + +#[tokio::test] +async fn ingest_stdin_tool_emits_error_v1_on_existing_frontmatter() { + let dir = tempfile::tempdir().unwrap(); + let state = fresh_state(dir.path()); + + let result = tokio::task::spawn_blocking({ + let state = state.clone(); + move || { + kebab_mcp::tools::ingest_stdin::handle( + &state, + kebab_mcp::tools::ingest_stdin::IngestStdinInput { + content: "---\ntitle: Existing\n---\n\n## Body".to_string(), + title: "New".to_string(), + source_uri: None, + }, + ) + } + }) + .await + .unwrap(); + + assert_eq!(result.is_error, Some(true), "{result:?}"); + let text = match &result.content.first().unwrap().raw { + RawContent::Text(t) => &t.text, + other => panic!("expected text content, got {other:?}"), + }; + let v: serde_json::Value = serde_json::from_str(text).unwrap(); + assert_eq!( + v.get("schema_version").and_then(|s| s.as_str()), + Some("error.v1") + ); +}