use super::*;
#[test]
fn quickstart_minimal_config_validates() {
let mut config = Config::default();
config.business = BusinessProfile::quickstart(
"MyApp".to_string(),
vec!["rust cli".to_string(), "developer tools".to_string()],
);
config.business.product_description = "A developer tool".to_string();
config.llm.provider = "ollama".to_string();
config.x_api.client_id = "test-client-id".to_string();
assert!(config.validate().is_ok());
}
#[test]
fn quickstart_industry_topics_derived_from_keywords() {
let profile = BusinessProfile {
product_keywords: vec!["rust".to_string(), "cli".to_string()],
industry_topics: vec![],
..Default::default()
};
assert_eq!(
profile.effective_industry_topics(),
&["rust".to_string(), "cli".to_string()]
);
}
#[test]
fn explicit_industry_topics_override_derived() {
let profile = BusinessProfile {
product_keywords: vec!["rust".to_string()],
industry_topics: vec!["Rust development".to_string()],
..Default::default()
};
assert_eq!(
profile.effective_industry_topics(),
&["Rust development".to_string()]
);
}
#[test]
fn is_enriched_false_for_quickstart() {
let profile = BusinessProfile::quickstart("App".to_string(), vec!["test".to_string()]);
assert!(!profile.is_enriched());
}
#[test]
fn is_enriched_true_with_brand_voice() {
let mut profile = BusinessProfile::quickstart("App".to_string(), vec!["test".to_string()]);
profile.brand_voice = Some("Friendly and casual".to_string());
assert!(profile.is_enriched());
}
#[test]
fn draft_context_keywords_empty_profile() {
let profile = BusinessProfile::default();
assert!(profile.draft_context_keywords().is_empty());
}
#[test]
fn draft_context_keywords_only_product() {
let profile = BusinessProfile {
product_keywords: vec!["rust".to_string(), "cli".to_string()],
..Default::default()
};
let keywords = profile.draft_context_keywords();
assert_eq!(
keywords,
vec!["rust", "cli", "rust", "cli"],
"product_keywords duplicated via effective_industry_topics fallback"
);
}
#[test]
fn draft_context_keywords_all_keyword_types() {
let profile = BusinessProfile {
product_keywords: vec!["rust".to_string()],
competitor_keywords: vec!["go".to_string(), "python".to_string()],
industry_topics: vec!["systems programming".to_string()],
..Default::default()
};
let keywords = profile.draft_context_keywords();
assert_eq!(
keywords,
vec!["rust", "go", "python", "systems programming"]
);
}
#[test]
fn draft_context_keywords_deduplication_not_applied() {
let profile = BusinessProfile {
product_keywords: vec!["rust".to_string()],
competitor_keywords: vec!["rust".to_string(), "go".to_string()],
industry_topics: vec!["rust ecosystem".to_string()],
..Default::default()
};
let keywords = profile.draft_context_keywords();
assert_eq!(keywords, vec!["rust", "rust", "go", "rust ecosystem"]);
}
#[test]
fn content_sources_config_serde_roundtrip() {
let toml_str = r#"
[[content_sources.sources]]
source_type = "local_fs"
path = "~/notes/vault"
watch = true
file_patterns = ["*.md", "*.txt"]
loop_back_enabled = true
"#;
let config: Config = toml::from_str(toml_str).expect("valid TOML");
assert_eq!(config.content_sources.sources.len(), 1);
let source = &config.content_sources.sources[0];
assert_eq!(source.source_type, "local_fs");
assert_eq!(source.path.as_deref(), Some("~/notes/vault"));
assert!(source.watch);
assert_eq!(source.file_patterns, vec!["*.md", "*.txt"]);
assert!(source.loop_back_enabled);
}
#[test]
fn content_sources_defaults() {
let toml_str = r#"
[[content_sources.sources]]
path = "~/notes"
"#;
let config: Config = toml::from_str(toml_str).expect("valid TOML");
let source = &config.content_sources.sources[0];
assert_eq!(source.source_type, "local_fs");
assert!(source.watch);
assert_eq!(source.file_patterns, vec!["*.md", "*.txt"]);
assert!(source.loop_back_enabled);
}
#[test]
fn content_sources_optional_in_config() {
let toml_str = r#"
[business]
product_name = "Test"
"#;
let config: Config = toml::from_str(toml_str).expect("valid TOML");
assert!(config.content_sources.sources.is_empty());
}
#[test]
fn content_sources_json_patch_roundtrip() {
let patch = serde_json::json!({
"content_sources": {
"sources": [{
"source_type": "local_fs",
"path": "~/notes/vault",
"watch": true,
"file_patterns": ["*.md", "*.txt"],
"loop_back_enabled": true
}]
}
});
let config: Config = serde_json::from_value(patch).expect("valid JSON");
assert_eq!(config.content_sources.sources.len(), 1);
let source = &config.content_sources.sources[0];
assert_eq!(source.source_type, "local_fs");
assert_eq!(source.path.as_deref(), Some("~/notes/vault"));
assert!(source.watch);
assert_eq!(source.file_patterns, vec!["*.md", "*.txt"]);
assert!(source.loop_back_enabled);
let toml_str = toml::to_string_pretty(&config).expect("serialize to TOML");
let roundtripped: Config = toml::from_str(&toml_str).expect("re-parse TOML");
assert_eq!(roundtripped.content_sources.sources.len(), 1);
assert_eq!(
roundtripped.content_sources.sources[0].path.as_deref(),
Some("~/notes/vault")
);
}
#[test]
fn content_sources_empty_json_patch() {
let patch = serde_json::json!({
"content_sources": {
"sources": []
}
});
let config: Config = serde_json::from_value(patch).expect("valid JSON");
assert!(config.content_sources.sources.is_empty());
}
#[test]
fn content_sources_google_drive_roundtrip() {
let toml_str = r#"
[[content_sources.sources]]
source_type = "google_drive"
folder_id = "1aBcD_eFgHiJkLmNoPqRsTuVwXyZ"
service_account_key = "~/keys/my-project-sa.json"
watch = true
file_patterns = ["*.md", "*.txt"]
loop_back_enabled = false
poll_interval_seconds = 600
"#;
let config: Config = toml::from_str(toml_str).expect("valid TOML");
assert_eq!(config.content_sources.sources.len(), 1);
let source = &config.content_sources.sources[0];
assert_eq!(source.source_type, "google_drive");
assert_eq!(
source.folder_id.as_deref(),
Some("1aBcD_eFgHiJkLmNoPqRsTuVwXyZ")
);
assert_eq!(
source.service_account_key.as_deref(),
Some("~/keys/my-project-sa.json")
);
assert!(source.path.is_none());
assert!(!source.loop_back_enabled);
assert_eq!(source.poll_interval_seconds, Some(600));
let toml_out = toml::to_string_pretty(&config).expect("serialize");
let roundtripped: Config = toml::from_str(&toml_out).expect("re-parse");
let rt_src = &roundtripped.content_sources.sources[0];
assert_eq!(rt_src.source_type, "google_drive");
assert_eq!(
rt_src.folder_id.as_deref(),
Some("1aBcD_eFgHiJkLmNoPqRsTuVwXyZ")
);
}
#[test]
fn content_sources_mixed_sources_roundtrip() {
let toml_str = r#"
[[content_sources.sources]]
source_type = "local_fs"
path = "~/notes/vault"
watch = true
file_patterns = ["*.md"]
loop_back_enabled = true
[[content_sources.sources]]
source_type = "google_drive"
folder_id = "abc123"
service_account_key = "/etc/keys/sa.json"
watch = true
file_patterns = ["*.md", "*.txt"]
loop_back_enabled = false
poll_interval_seconds = 300
"#;
let config: Config = toml::from_str(toml_str).expect("valid TOML");
assert_eq!(config.content_sources.sources.len(), 2);
assert_eq!(config.content_sources.sources[0].source_type, "local_fs");
assert_eq!(
config.content_sources.sources[0].path.as_deref(),
Some("~/notes/vault")
);
assert!(config.content_sources.sources[0].folder_id.is_none());
assert_eq!(
config.content_sources.sources[1].source_type,
"google_drive"
);
assert_eq!(
config.content_sources.sources[1].folder_id.as_deref(),
Some("abc123")
);
assert!(config.content_sources.sources[1].path.is_none());
}
#[test]
fn content_sources_google_drive_json_patch() {
let patch = serde_json::json!({
"content_sources": {
"sources": [{
"source_type": "google_drive",
"path": null,
"folder_id": "drive_folder_abc",
"service_account_key": "/path/to/key.json",
"watch": true,
"file_patterns": ["*.md", "*.txt"],
"loop_back_enabled": false,
"poll_interval_seconds": 300
}]
}
});
let config: Config = serde_json::from_value(patch).expect("valid JSON");
assert_eq!(config.content_sources.sources.len(), 1);
let source = &config.content_sources.sources[0];
assert_eq!(source.source_type, "google_drive");
assert_eq!(source.folder_id.as_deref(), Some("drive_folder_abc"));
assert_eq!(
source.service_account_key.as_deref(),
Some("/path/to/key.json")
);
assert!(source.path.is_none());
assert_eq!(source.poll_interval_seconds, Some(300));
}