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")
+ );
+}