use kimun_core::nfs::VaultPath;
use kimun_core::{NoteVault, VaultConfig};
use kimun_notes::cli::output::OutputFormat;
use kimun_notes::cli::{CliCommand, run_cli};
use tempfile::TempDir;
async fn setup_json_test_vault(dir: &TempDir) -> NoteVault {
let vault = NoteVault::new(VaultConfig::new(dir.path()))
.await
.expect("failed to create vault");
vault
.validate_and_init()
.await
.expect("failed to init vault");
vault
.create_note(
&VaultPath::note_path_from("rust-intro"),
"# Introduction to Rust\n\n#programming #rust\n\nRust is a systems programming language.\n\n[[memory-safety]] [[ownership]]",
)
.await
.expect("failed to create rust note");
vault
.create_note(
&VaultPath::note_path_from("python-basics"),
"# Python Basics\n\n#programming #python\n\nPython is great for scripting.",
)
.await
.expect("failed to create python note");
vault
.create_note(
&VaultPath::note_path_from("notes/deep-dive"),
"# Deep Dive\n\n## Overview\n\nA nested note for testing path filtering.",
)
.await
.expect("failed to create deep dive note");
vault
.recreate_index()
.await
.expect("failed to recreate index");
vault
}
fn write_config(config_path: &std::path::Path, workspace: &std::path::Path) {
let toml = format!(
"workspace_dir = {:?}\n",
workspace.to_string_lossy().as_ref()
);
std::fs::write(config_path, toml).expect("failed to write config file");
}
#[tokio::test]
async fn test_search_json_output_is_valid() {
let workspace_dir = TempDir::new().unwrap();
let config_dir = TempDir::new().unwrap();
let config_path = config_dir.path().join("config.toml");
setup_json_test_vault(&workspace_dir).await;
write_config(&config_path, workspace_dir.path());
let vault = NoteVault::new(VaultConfig::new(workspace_dir.path()))
.await
.unwrap();
vault.validate_and_init().await.unwrap();
let results = vault.search_notes("rust").await.unwrap();
let json_str = kimun_notes::cli::json_output::format_notes_as_json(
&vault,
&results,
"default",
Some("rust"),
false, )
.await
.expect("format_notes_as_json should succeed");
let json: serde_json::Value =
serde_json::from_str(&json_str).expect("output should be valid JSON");
assert!(json["metadata"].is_object(), "should have metadata object");
assert!(json["notes"].is_array(), "should have notes array");
assert_eq!(json["metadata"]["workspace"], "default");
assert_eq!(
json["metadata"]["workspace_path"],
workspace_dir.path().to_string_lossy().to_string()
);
assert_eq!(json["metadata"]["query"], "rust");
assert_eq!(json["metadata"]["is_listing"], false);
assert!(json["metadata"]["total_results"].as_u64().unwrap() >= 1);
assert!(json["metadata"]["generated_at"].is_string());
let note = &json["notes"][0];
assert!(note["path"].is_string(), "note should have path");
assert!(note["title"].is_string(), "note should have title");
assert!(note["size"].is_number(), "note should have size");
assert!(note["modified"].is_number(), "note should have modified");
assert!(note["hash"].is_string(), "note should have hash");
assert!(
note["metadata"].is_object(),
"note should have metadata object"
);
assert!(
note["metadata"]["tags"].is_array(),
"metadata should have tags"
);
assert!(
note["metadata"]["links"].is_array(),
"metadata should have links"
);
assert!(
note["metadata"]["headers"].is_array(),
"metadata should have headers"
);
}
#[tokio::test]
async fn test_notes_json_output_is_valid() {
let workspace_dir = TempDir::new().unwrap();
let config_dir = TempDir::new().unwrap();
let config_path = config_dir.path().join("config.toml");
setup_json_test_vault(&workspace_dir).await;
write_config(&config_path, workspace_dir.path());
let vault = NoteVault::new(VaultConfig::new(workspace_dir.path()))
.await
.unwrap();
vault.validate_and_init().await.unwrap();
let results = vault.get_all_notes().await.unwrap();
let json_str = kimun_notes::cli::json_output::format_notes_as_json(
&vault,
&results,
"my-workspace",
None,
true, )
.await
.expect("format_notes_as_json should succeed");
let json: serde_json::Value =
serde_json::from_str(&json_str).expect("output should be valid JSON");
assert_eq!(json["metadata"]["workspace"], "my-workspace");
assert_eq!(json["metadata"]["is_listing"], true);
assert!(
json["metadata"]["query"].is_null(),
"query should be null for listing"
);
assert!(json["notes"].as_array().unwrap().len() >= 3);
for note in json["notes"].as_array().unwrap() {
assert!(
note["metadata"].is_object(),
"each note should have metadata: {:?}",
note["path"]
);
}
}
#[tokio::test]
async fn test_search_json_metadata_contains_tags_and_links() {
let workspace_dir = TempDir::new().unwrap();
let config_dir = TempDir::new().unwrap();
let config_path = config_dir.path().join("config.toml");
setup_json_test_vault(&workspace_dir).await;
write_config(&config_path, workspace_dir.path());
let vault = NoteVault::new(VaultConfig::new(workspace_dir.path()))
.await
.unwrap();
vault.validate_and_init().await.unwrap();
let results = vault.search_notes("rust").await.unwrap();
let json_str = kimun_notes::cli::json_output::format_notes_as_json(
&vault,
&results,
"default",
Some("rust"),
false, )
.await
.expect("format_notes_as_json should succeed");
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
let notes = json["notes"].as_array().unwrap();
assert!(!notes.is_empty(), "should find the rust note");
let rust_note = notes.iter().find(|n| {
n["path"]
.as_str()
.map(|p| p.contains("rust"))
.unwrap_or(false)
});
assert!(rust_note.is_some(), "should find rust-intro note");
let note = rust_note.unwrap();
let tags = note["metadata"]["tags"].as_array().unwrap();
assert!(
tags.iter()
.any(|t| t.as_str() == Some("programming") || t.as_str() == Some("rust")),
"should extract tags from content; found: {:?}",
tags
);
let links = note["metadata"]["links"].as_array().unwrap();
assert!(
links.iter().any(|l| l
.as_str()
.map(|s| s.contains("memory-safety") || s.contains("ownership"))
.unwrap_or(false)),
"should extract wiki links from content; found: {:?}",
links
);
}
#[tokio::test]
async fn test_notes_json_journal_date_field_present() {
let workspace_dir = TempDir::new().unwrap();
let config_dir = TempDir::new().unwrap();
let config_path = config_dir.path().join("config.toml");
setup_json_test_vault(&workspace_dir).await;
write_config(&config_path, workspace_dir.path());
let vault = NoteVault::new(VaultConfig::new(workspace_dir.path()))
.await
.unwrap();
vault.validate_and_init().await.unwrap();
let journal_path = VaultPath::note_path_from("journal/2024-01-15");
vault
.create_note(
&journal_path,
"# January 15, 2024\n\nToday's journal entry.",
)
.await
.expect("failed to create journal note");
vault.recreate_index().await.unwrap();
let results = vault.get_all_notes().await.unwrap();
let json_str = kimun_notes::cli::json_output::format_notes_as_json(
&vault, &results, "default", None, true, )
.await
.expect("format_notes_as_json should succeed");
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
let notes = json["notes"].as_array().unwrap();
let journal_note = notes.iter().find(|n| {
n["path"]
.as_str()
.map(|p| p.contains("journal"))
.unwrap_or(false)
});
assert!(journal_note.is_some(), "should find journal note");
let note = journal_note.unwrap();
assert!(
note["journal_date"].is_string(),
"journal notes should have journal_date field set; got: {:?}",
note["journal_date"]
);
assert_eq!(note["journal_date"], "2024-01-15");
}
#[tokio::test]
async fn test_notes_json_created_field_present() {
let workspace_dir = TempDir::new().unwrap();
setup_json_test_vault(&workspace_dir).await;
let vault = NoteVault::new(VaultConfig::new(workspace_dir.path()))
.await
.unwrap();
vault.validate_and_init().await.unwrap();
let results = vault.get_all_notes().await.unwrap();
let json_str = kimun_notes::cli::json_output::format_notes_as_json(
&vault, &results, "default", None, true, )
.await
.expect("format_notes_as_json should succeed");
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
let notes = json["notes"].as_array().unwrap();
assert!(!notes.is_empty());
for note in notes {
assert!(
note["created"].is_number(),
"note should have created field; note: {:?}",
note["path"]
);
}
}
#[tokio::test]
async fn test_search_json_via_run_cli() {
let workspace_dir = TempDir::new().unwrap();
let config_dir = TempDir::new().unwrap();
let config_path = config_dir.path().join("config.toml");
setup_json_test_vault(&workspace_dir).await;
write_config(&config_path, workspace_dir.path());
let result = run_cli(
CliCommand::Search {
query: "rust".to_string(),
format: OutputFormat::Json,
},
Some(config_path),
)
.await;
assert!(
result.is_ok(),
"search with JSON format should succeed: {:?}",
result
);
}
#[tokio::test]
async fn test_notes_json_via_run_cli() {
let workspace_dir = TempDir::new().unwrap();
let config_dir = TempDir::new().unwrap();
let config_path = config_dir.path().join("config.toml");
setup_json_test_vault(&workspace_dir).await;
write_config(&config_path, workspace_dir.path());
let result = run_cli(
CliCommand::Notes {
path: None,
format: OutputFormat::Json,
},
Some(config_path),
)
.await;
assert!(
result.is_ok(),
"notes with JSON format should succeed: {:?}",
result
);
}
#[tokio::test]
async fn test_text_format_unaffected() {
let workspace_dir = TempDir::new().unwrap();
let config_dir = TempDir::new().unwrap();
let config_path = config_dir.path().join("config.toml");
setup_json_test_vault(&workspace_dir).await;
write_config(&config_path, workspace_dir.path());
let result = run_cli(
CliCommand::Search {
query: "python".to_string(),
format: OutputFormat::Text,
},
Some(config_path.clone()),
)
.await;
assert!(
result.is_ok(),
"search text format should still work: {:?}",
result
);
let result = run_cli(
CliCommand::Notes {
path: None,
format: OutputFormat::Text,
},
Some(config_path),
)
.await;
assert!(
result.is_ok(),
"notes text format should still work: {:?}",
result
);
}