use super::*;
use crate::theme;
use proptest::prelude::*;
#[test]
fn test_suggestion_type_colors() {
assert_eq!(SuggestionType::Fix.color(), theme::ai::SUGGESTION_FIX);
assert_eq!(
SuggestionType::Optimize.color(),
theme::ai::SUGGESTION_OPTIMIZE
);
assert_eq!(SuggestionType::Next.color(), theme::ai::SUGGESTION_NEXT);
}
#[test]
fn test_suggestion_type_from_str() {
assert_eq!(SuggestionType::parse_type("Fix"), Some(SuggestionType::Fix));
assert_eq!(SuggestionType::parse_type("fix"), Some(SuggestionType::Fix));
assert_eq!(SuggestionType::parse_type("FIX"), Some(SuggestionType::Fix));
assert_eq!(
SuggestionType::parse_type("Optimize"),
Some(SuggestionType::Optimize)
);
assert_eq!(
SuggestionType::parse_type("Next"),
Some(SuggestionType::Next)
);
assert_eq!(SuggestionType::parse_type("Invalid"), None);
}
#[test]
fn test_suggestion_type_labels() {
assert_eq!(SuggestionType::Fix.label(), "[Fix]");
assert_eq!(SuggestionType::Optimize.label(), "[Optimize]");
assert_eq!(SuggestionType::Next.label(), "[Next]");
}
#[test]
fn test_parse_suggestions_single_json() {
let response = r#"{"suggestions": [{"type": "fix", "query": ".users[] | select(.active)", "details": "Filters to only active users"}]}"#;
let suggestions = parse_suggestions(response);
assert_eq!(suggestions.len(), 1);
assert_eq!(suggestions[0].query, ".users[] | select(.active)");
assert_eq!(suggestions[0].description, "Filters to only active users");
assert_eq!(suggestions[0].suggestion_type, SuggestionType::Fix);
}
#[test]
fn test_parse_suggestions_single_legacy_text() {
let response = "1. [Fix] .users[] | select(.active)\n Filters to only active users";
let suggestions = parse_suggestions(response);
assert_eq!(suggestions.len(), 1);
assert_eq!(suggestions[0].query, ".users[] | select(.active)");
assert_eq!(suggestions[0].description, "Filters to only active users");
assert_eq!(suggestions[0].suggestion_type, SuggestionType::Fix);
}
#[test]
fn test_parse_suggestions_multiple_json() {
let response = r#"{
"suggestions": [
{"type": "fix", "query": ".users[] | select(.active)", "details": "Filters to only active users"},
{"type": "next", "query": ".users[] | .email", "details": "Extracts email addresses"},
{"type": "optimize", "query": ".users | map(.name)", "details": "More efficient mapping"}
]
}"#;
let suggestions = parse_suggestions(response);
assert_eq!(suggestions.len(), 3);
assert_eq!(suggestions[0].query, ".users[] | select(.active)");
assert_eq!(suggestions[0].suggestion_type, SuggestionType::Fix);
assert_eq!(suggestions[1].query, ".users[] | .email");
assert_eq!(suggestions[1].suggestion_type, SuggestionType::Next);
assert_eq!(suggestions[2].query, ".users | map(.name)");
assert_eq!(suggestions[2].suggestion_type, SuggestionType::Optimize);
}
#[test]
fn test_parse_suggestions_multiple_legacy_text() {
let response = r#"1. [Fix] .users[] | select(.active)
Filters to only active users
2. [Next] .users[] | .email
Extracts email addresses
3. [Optimize] .users | map(.name)
More efficient mapping"#;
let suggestions = parse_suggestions(response);
assert_eq!(suggestions.len(), 3);
assert_eq!(suggestions[0].query, ".users[] | select(.active)");
assert_eq!(suggestions[0].suggestion_type, SuggestionType::Fix);
assert_eq!(suggestions[1].query, ".users[] | .email");
assert_eq!(suggestions[1].suggestion_type, SuggestionType::Next);
assert_eq!(suggestions[2].query, ".users | map(.name)");
assert_eq!(suggestions[2].suggestion_type, SuggestionType::Optimize);
}
#[test]
fn test_parse_suggestions_multiline_description_legacy() {
let response =
"1. [Fix] .data[]\n This is a longer description\n that spans multiple lines";
let suggestions = parse_suggestions(response);
assert_eq!(suggestions.len(), 1);
assert_eq!(suggestions[0].query, ".data[]");
assert!(suggestions[0].description.contains("longer description"));
assert!(suggestions[0].description.contains("multiple lines"));
}
#[test]
fn test_parse_suggestions_empty_response() {
let suggestions = parse_suggestions("");
assert!(suggestions.is_empty());
}
#[test]
fn test_parse_suggestions_with_backticks_legacy() {
let response = "1. [Fix] `.users[] | select(.active)`\n Filters to only active users";
let suggestions = parse_suggestions(response);
assert_eq!(suggestions.len(), 1);
assert_eq!(suggestions[0].query, ".users[] | select(.active)");
assert_eq!(suggestions[0].description, "Filters to only active users");
assert_eq!(suggestions[0].suggestion_type, SuggestionType::Fix);
}
#[test]
fn test_parse_suggestions_invalid_json() {
let response = r#"{"suggestions": [{"type": "fix" INVALID JSON"#;
let suggestions = parse_suggestions(response);
assert!(suggestions.is_empty());
}
#[test]
fn test_parse_suggestions_missing_field() {
let response = r#"{"suggestions": [{"type": "fix", "query": ".users[]"}]}"#;
let suggestions = parse_suggestions(response);
assert!(suggestions.is_empty());
}
#[test]
fn test_parse_suggestions_invalid_type() {
let response =
r#"{"suggestions": [{"type": "invalid", "query": ".users[]", "details": "test"}]}"#;
let suggestions = parse_suggestions(response);
assert!(suggestions.is_empty());
}
#[test]
fn test_parse_suggestions_with_markdown_fences() {
let response = r#"```json
{
"suggestions": [
{"type": "fix", "query": ".users[]", "details": "Extract users"}
]
}
```"#;
let suggestions = parse_suggestions(response);
assert_eq!(suggestions.len(), 1);
assert_eq!(suggestions[0].query, ".users[]");
assert_eq!(suggestions[0].description, "Extract users");
assert_eq!(suggestions[0].suggestion_type, SuggestionType::Fix);
}
#[test]
fn test_parse_suggestions_with_markdown_fences_multiline() {
let response = r#"```json
{
"suggestions": [
{"type": "optimize", "query": ".users[] | select(.active)", "details": "Filter active users"},
{"type": "next", "query": ".users[] | .email", "details": "Get emails"}
]
}
```"#;
let suggestions = parse_suggestions(response);
assert_eq!(suggestions.len(), 2);
assert_eq!(suggestions[0].query, ".users[] | select(.active)");
assert_eq!(suggestions[1].query, ".users[] | .email");
}
#[test]
fn test_parse_suggestions_no_valid_format() {
let response = "This is just plain text without any structured suggestions.";
let suggestions = parse_suggestions(response);
assert!(suggestions.is_empty());
}
#[test]
fn test_parse_suggestions_malformed() {
let response = "1. Fix .users[]";
let suggestions = parse_suggestions(response);
assert!(suggestions.is_empty());
let response = "1. [Fix]";
let suggestions = parse_suggestions(response);
assert!(suggestions.is_empty());
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_suggestion_parsing_extracts_queries_json(
query in "\\.[a-zA-Z0-9_|\\[\\]]{1,30}",
desc in "[a-zA-Z ]{1,50}",
suggestion_type in prop::sample::select(vec!["fix", "optimize", "next"]),
) {
let response = format!(
r#"{{"suggestions": [{{"type": "{}", "query": "{}", "details": "{}"}}]}}"#,
suggestion_type, query, desc
);
let suggestions = parse_suggestions(&response);
prop_assert_eq!(suggestions.len(), 1, "Should parse exactly one suggestion");
prop_assert_eq!(&suggestions[0].query, query.trim(), "Query should match");
}
#[test]
fn prop_suggestion_parsing_extracts_queries_legacy(
query in "\\.[a-zA-Z0-9_|\\[\\]]{1,30}",
desc in "[a-zA-Z ]{1,50}",
suggestion_type in prop::sample::select(vec!["Fix", "Optimize", "Next"]),
) {
let response = format!("1. [{}] {}\n {}", suggestion_type, query, desc);
let suggestions = parse_suggestions(&response);
prop_assert_eq!(suggestions.len(), 1, "Should parse exactly one suggestion");
prop_assert_eq!(&suggestions[0].query, query.trim(), "Query should match");
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_malformed_response_returns_empty(
text in "[a-zA-Z ]{0,100}",
) {
let suggestions = parse_suggestions(&text);
prop_assert!(suggestions.len() <= 1, "Should handle any text gracefully");
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_suggestion_type_colors_correct(
type_idx in 0usize..3usize,
) {
let types = [SuggestionType::Fix, SuggestionType::Optimize, SuggestionType::Next];
let expected_colors = [
theme::ai::SUGGESTION_FIX,
theme::ai::SUGGESTION_OPTIMIZE,
theme::ai::SUGGESTION_NEXT,
];
let suggestion_type = types[type_idx];
let expected_color = expected_colors[type_idx];
prop_assert_eq!(
suggestion_type.color(),
expected_color,
"Color for {:?} should be {:?}",
suggestion_type,
expected_color
);
}
}