mod common;
use common::sqry_bin;
use assert_cmd::Command;
use serde_json::Value;
use std::fs;
use tempfile::TempDir;
fn create_test_project(files: &[(&str, &str)]) -> TempDir {
let dir = TempDir::new().unwrap();
for (path, content) in files {
let file_path = dir.path().join(path);
fs::create_dir_all(file_path.parent().unwrap()).unwrap();
fs::write(&file_path, content).unwrap();
}
dir
}
fn sqry_cmd() -> Command {
let path = sqry_bin();
Command::new(path)
}
fn parse_json_output(output: &str) -> Value {
serde_json::from_str(output).expect("Output should be valid JSON")
}
#[test]
fn test_json_unmatched_parenthesis_error() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("(kind:function AND name:test")
.arg(project.path())
.assert()
.failure()
.code(2) .get_output()
.stdout
.clone();
let json = parse_json_output(&String::from_utf8_lossy(&output));
assert!(json["error"].is_object(), "Should have 'error' object");
assert_eq!(json["error"]["code"], "sqry::parse");
assert!(
json["error"]["message"]
.as_str()
.unwrap()
.contains("Unmatched")
);
assert_eq!(json["error"]["query"], "(kind:function AND name:test");
assert!(
json["error"]["span"].is_object(),
"Should have span information"
);
assert!(json["error"]["span"]["start"].is_number());
assert!(json["error"]["span"]["end"].is_number());
assert!(json["error"]["label"].is_string(), "Should have label");
assert!(json["error"]["help"].is_string(), "Should have help text");
}
#[test]
fn test_json_unterminated_string_error() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg(r#"kind:function AND name:"test"#)
.arg(project.path())
.assert()
.failure()
.code(2)
.get_output()
.stdout
.clone();
let json = parse_json_output(&String::from_utf8_lossy(&output));
let code = json["error"]["code"].as_str().unwrap();
assert!(
code == "sqry::syntax" || code == "sqry::parse",
"Expected sqry::syntax or sqry::parse, got {code}"
);
assert!(
json["error"]["message"]
.as_str()
.unwrap()
.contains("Unterminated")
);
assert!(json["error"]["span"].is_object());
}
#[test]
fn test_json_unterminated_regex_error() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("kind:function AND name~=/test")
.arg(project.path())
.assert()
.failure()
.code(2)
.get_output()
.stdout
.clone();
let json = parse_json_output(&String::from_utf8_lossy(&output));
let code = json["error"]["code"].as_str().unwrap();
assert!(
code == "sqry::syntax" || code == "sqry::parse",
"Expected sqry::syntax or sqry::parse, got {code}"
);
assert!(
json["error"]["message"]
.as_str()
.unwrap()
.contains("Unterminated")
);
}
#[test]
fn test_json_unknown_field_with_suggestion() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("kind:function AND knd:test")
.arg(project.path())
.assert()
.failure()
.code(2)
.get_output()
.stdout
.clone();
let json = parse_json_output(&String::from_utf8_lossy(&output));
assert_eq!(json["error"]["code"], "sqry::validation");
assert!(
json["error"]["message"]
.as_str()
.unwrap()
.contains("Unknown field")
);
assert!(json["error"]["message"].as_str().unwrap().contains("knd"));
assert!(
json["error"]["suggestion"].is_string(),
"Should have suggestion"
);
assert_eq!(json["error"]["suggestion"], "kind");
assert!(
json["error"]["help"]
.as_str()
.unwrap()
.contains("Did you mean 'kind'?")
);
}
#[test]
fn test_json_invalid_enum_value() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("kind:invalid_kind")
.arg(project.path())
.assert()
.failure()
.code(2)
.get_output()
.stdout
.clone();
let json = parse_json_output(&String::from_utf8_lossy(&output));
assert_eq!(json["error"]["code"], "sqry::validation");
assert!(
json["error"]["message"]
.as_str()
.unwrap()
.contains("Invalid value")
);
assert!(
json["error"]["message"]
.as_str()
.unwrap()
.contains("invalid_kind")
);
let help = json["error"]["help"].as_str().unwrap();
assert!(help.contains("Valid values:"));
assert!(help.contains("function"));
assert!(help.contains("method"));
assert!(help.contains("class"));
}
#[test]
fn test_json_unknown_field_no_suggestion() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("kind:function AND xyz:test")
.arg(project.path())
.assert()
.failure()
.code(2)
.get_output()
.stdout
.clone();
let json = parse_json_output(&String::from_utf8_lossy(&output));
assert_eq!(json["error"]["code"], "sqry::validation");
assert!(
json["error"]["message"]
.as_str()
.unwrap()
.contains("Unknown field")
);
assert!(json["error"]["message"].as_str().unwrap().contains("xyz"));
if json["error"]["suggestion"].is_string() {
assert_ne!(json["error"]["suggestion"].as_str().unwrap(), "xyz");
}
}
#[test]
fn test_json_output_is_valid_json() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("(kind:function")
.arg(project.path())
.assert()
.failure()
.code(2)
.get_output()
.stdout
.clone();
let _json = parse_json_output(&String::from_utf8_lossy(&output));
}
#[test]
fn test_json_error_has_required_fields() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("(kind:function AND name:test")
.arg(project.path())
.assert()
.failure()
.code(2)
.get_output()
.stdout
.clone();
let json = parse_json_output(&String::from_utf8_lossy(&output));
assert!(json["error"].is_object());
assert!(json["error"]["code"].is_string());
assert!(json["error"]["message"].is_string());
assert!(json["error"]["query"].is_string());
}
#[test]
fn test_json_span_has_start_and_end() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("kind:function AND knd:test")
.arg(project.path())
.assert()
.failure()
.code(2)
.get_output()
.stdout
.clone();
let json = parse_json_output(&String::from_utf8_lossy(&output));
assert!(json["error"]["span"]["start"].is_number());
assert!(json["error"]["span"]["end"].is_number());
let start = json["error"]["span"]["start"].as_u64().unwrap();
let end = json["error"]["span"]["end"].as_u64().unwrap();
assert!(end > start, "End position should be after start position");
}
#[test]
fn test_json_parse_error_code() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("(kind:function")
.arg(project.path())
.assert()
.failure()
.code(2) .get_output()
.stdout
.clone();
let json = parse_json_output(&String::from_utf8_lossy(&output));
assert_eq!(json["error"]["code"], "sqry::parse");
}
#[test]
fn test_json_validation_error_code() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("kind:function AND knd:test")
.arg(project.path())
.assert()
.failure()
.code(2) .get_output()
.stdout
.clone();
let json = parse_json_output(&String::from_utf8_lossy(&output));
assert_eq!(json["error"]["code"], "sqry::validation");
}
#[test]
fn test_json_flag_changes_output_format() {
let project = create_test_project(&[("test.rs", "fn test() {}")]);
let terminal_output = sqry_cmd()
.arg("query")
.arg("(kind:function")
.arg(project.path())
.assert()
.failure()
.get_output()
.stderr .clone();
let terminal_str = String::from_utf8_lossy(&terminal_output);
assert!(
terminal_str.contains("│") || terminal_str.contains("╭") || terminal_str.contains("Error")
);
assert!(!terminal_str.contains(r#""error""#));
let json_output = sqry_cmd()
.arg("query")
.arg("--json")
.arg("(kind:function")
.arg(project.path())
.assert()
.failure()
.get_output()
.stdout .clone();
let json_str = String::from_utf8_lossy(&json_output);
let _json = serde_json::from_str::<Value>(&json_str).expect("Should be valid JSON");
}