#![allow(clippy::needless_collect)]
use aperture_cli::cache::models::{
CachedCommand, CachedParameter, CachedSpec, PaginationInfo, CACHE_FORMAT_VERSION,
};
use aperture_cli::search::{format_search_results, CommandSearcher};
use std::collections::{BTreeMap, HashMap};
#[allow(clippy::too_many_lines)]
fn create_test_spec(name: &str) -> CachedSpec {
CachedSpec {
cache_format_version: CACHE_FORMAT_VERSION,
name: name.to_string(),
version: "1.0.0".to_string(),
base_url: Some("https://api.example.com".to_string()),
commands: vec![
CachedCommand {
operation_id: "getUser".to_string(),
method: "GET".to_string(),
path: "/users/{id}".to_string(),
summary: Some("Get a user by ID".to_string()),
description: Some("Retrieves detailed user information".to_string()),
tags: vec!["users".to_string()],
name: "users".to_string(),
parameters: vec![CachedParameter {
name: "id".to_string(),
location: "path".to_string(),
required: true,
description: Some("User ID".to_string()),
schema: None,
schema_type: Some("string".to_string()),
format: None,
default_value: None,
enum_values: vec![],
example: None,
}],
request_body: None,
responses: vec![],
security_requirements: vec![],
deprecated: false,
external_docs_url: None,
examples: vec![],
display_group: None,
display_name: None,
aliases: vec![],
hidden: false,
pagination: PaginationInfo::default(),
},
CachedCommand {
operation_id: "listUsers".to_string(),
method: "GET".to_string(),
path: "/users".to_string(),
summary: Some("List all users".to_string()),
description: Some("Returns a paginated list of users".to_string()),
tags: vec!["users".to_string()],
name: "users".to_string(),
parameters: vec![],
request_body: None,
responses: vec![],
security_requirements: vec![],
deprecated: false,
external_docs_url: None,
examples: vec![],
display_group: None,
display_name: None,
aliases: vec![],
hidden: false,
pagination: PaginationInfo::default(),
},
CachedCommand {
operation_id: "createUser".to_string(),
method: "POST".to_string(),
path: "/users".to_string(),
summary: Some("Create a new user".to_string()),
description: Some("Creates a new user account".to_string()),
tags: vec!["users".to_string()],
name: "users".to_string(),
parameters: vec![],
request_body: None,
responses: vec![],
security_requirements: vec![],
deprecated: false,
external_docs_url: None,
examples: vec![],
display_group: None,
display_name: None,
aliases: vec![],
hidden: false,
pagination: PaginationInfo::default(),
},
CachedCommand {
operation_id: "getIssue".to_string(),
method: "GET".to_string(),
path: "/issues/{id}".to_string(),
summary: Some("Get an issue by ID".to_string()),
description: Some("Retrieves issue details".to_string()),
tags: vec!["issues".to_string()],
name: "issues".to_string(),
parameters: vec![],
request_body: None,
responses: vec![],
security_requirements: vec![],
deprecated: false,
external_docs_url: None,
examples: vec![],
display_group: None,
display_name: None,
aliases: vec![],
hidden: false,
pagination: PaginationInfo::default(),
},
],
servers: vec![],
security_schemes: HashMap::new(),
skipped_endpoints: vec![],
server_variables: HashMap::new(),
}
}
#[test]
fn test_search_by_operation_id() {
let searcher = CommandSearcher::new();
let mut specs = BTreeMap::new();
let spec = create_test_spec("test-api");
for cmd in &spec.commands {
eprintln!(
"Command: operation_id={}, method={}, path={}",
cmd.operation_id, cmd.method, cmd.path
);
}
specs.insert("test-api".to_string(), spec);
let results = searcher.search(&specs, "getUser", None).unwrap();
eprintln!("Search for 'getUser' found {} results", results.len());
for result in &results {
eprintln!(
" - {} (score: {})",
result.command.operation_id, result.score
);
}
assert_eq!(results.len(), 1);
assert_eq!(results[0].command.operation_id, "getUser");
assert_eq!(results[0].api_context, "test-api");
}
#[test]
fn test_search_by_method() {
let searcher = CommandSearcher::new();
let mut specs = BTreeMap::new();
specs.insert("test-api".to_string(), create_test_spec("test-api"));
let results = searcher.search(&specs, "POST", None).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].command.method, "POST");
}
#[test]
fn test_search_by_keyword_in_summary() {
let searcher = CommandSearcher::new();
let mut specs = BTreeMap::new();
specs.insert("test-api".to_string(), create_test_spec("test-api"));
let results = searcher.search(&specs, "paginated", None).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].command.operation_id, "listUsers");
}
#[test]
fn test_search_with_api_filter() {
let searcher = CommandSearcher::new();
let mut specs = BTreeMap::new();
specs.insert("api1".to_string(), create_test_spec("api1"));
specs.insert("api2".to_string(), create_test_spec("api2"));
let results = searcher.search(&specs, "user", None).unwrap();
assert!(results.len() > 3);
let results = searcher.search(&specs, "user", Some("api1")).unwrap();
assert!(results.iter().all(|r| r.api_context == "api1"));
}
#[test]
fn test_fuzzy_search() {
let searcher = CommandSearcher::new();
let mut specs = BTreeMap::new();
specs.insert("test-api".to_string(), create_test_spec("test-api"));
let results = searcher.search(&specs, "user", None).unwrap();
assert!(
!results.is_empty(),
"Fuzzy search should find results for partial matches"
);
let user_ops: Vec<_> = results
.iter()
.filter(|r| r.command.operation_id.to_lowercase().contains("user"))
.collect();
assert!(
user_ops.len() >= 2,
"Should find at least 2 user operations"
);
let exact_results = searcher.search(&specs, "getUser", None).unwrap();
assert!(!exact_results.is_empty());
assert_eq!(exact_results[0].command.operation_id, "getUser");
let list_results = searcher.search(&specs, "list", None).unwrap();
assert!(!list_results.is_empty(), "Should find list operations");
}
#[test]
fn test_regex_search() {
let searcher = CommandSearcher::new();
let mut specs = BTreeMap::new();
specs.insert("test-api".to_string(), create_test_spec("test-api"));
let results = searcher.search(&specs, r"get\w+", None).unwrap();
assert_eq!(results.len(), 2); assert!(results
.iter()
.all(|r| r.command.operation_id.starts_with("get")));
}
#[test]
fn test_find_similar_commands() {
let searcher = CommandSearcher::new();
let spec = create_test_spec("test");
let suggestions = searcher.find_similar_commands(&spec, "usr get-usr", 3);
assert!(!suggestions.is_empty());
assert!(suggestions[0].0.contains("get-user"));
}
#[test]
fn test_format_search_results() {
let searcher = CommandSearcher::new();
let mut specs = BTreeMap::new();
specs.insert("test-api".to_string(), create_test_spec("test-api"));
let results = searcher.search(&specs, "getUser", None).unwrap();
let output = format_search_results(&results, false);
assert!(output
.iter()
.any(|line| line.contains("aperture api test-api")));
assert!(output.iter().any(|line| line.contains("GET /users/{id}")));
let output = format_search_results(&results, true);
assert!(output.iter().any(|line| line.contains("Parameters:")));
}
#[test]
fn test_empty_search_results() {
let results = vec![];
let output = format_search_results(&results, false);
assert_eq!(output.len(), 1);
assert_eq!(output[0], "No matching operations found.");
}
#[test]
fn test_search_scoring() {
let searcher = CommandSearcher::new();
let mut specs = BTreeMap::new();
specs.insert("test-api".to_string(), create_test_spec("test-api"));
let results = searcher.search(&specs, "user", None).unwrap();
assert!(!results.is_empty());
for i in 1..results.len() {
assert!(results[i - 1].score >= results[i].score);
}
}
#[test]
fn test_search_finds_by_display_name() {
let spec = CachedSpec {
cache_format_version: CACHE_FORMAT_VERSION,
name: "mapped-api".to_string(),
version: "1.0.0".to_string(),
commands: vec![CachedCommand {
operation_id: "getUserById".to_string(),
name: "users".to_string(),
description: Some("Fetch a user".to_string()),
summary: None,
method: "GET".to_string(),
path: "/users/{id}".to_string(),
parameters: vec![],
request_body: None,
responses: vec![],
security_requirements: vec![],
tags: vec!["users".to_string()],
deprecated: false,
external_docs_url: None,
examples: vec![],
display_group: None,
display_name: Some("fetch".to_string()),
aliases: vec![],
hidden: false,
pagination: PaginationInfo::default(),
}],
base_url: Some("https://api.example.com".to_string()),
servers: vec![],
security_schemes: HashMap::new(),
skipped_endpoints: vec![],
server_variables: HashMap::new(),
};
let searcher = CommandSearcher::new();
let mut specs = BTreeMap::new();
specs.insert("mapped-api".to_string(), spec);
let results = searcher.search(&specs, "fetch", None).unwrap();
assert!(!results.is_empty(), "Should find command by display name");
assert_eq!(results[0].command.operation_id, "getUserById");
assert!(
results[0].command_path.contains("fetch"),
"Command path should use display name, got: {}",
results[0].command_path
);
}
#[test]
fn test_search_finds_by_alias() {
let spec = CachedSpec {
cache_format_version: CACHE_FORMAT_VERSION,
name: "mapped-api".to_string(),
version: "1.0.0".to_string(),
commands: vec![CachedCommand {
operation_id: "getUserById".to_string(),
name: "users".to_string(),
description: Some("Fetch a user".to_string()),
summary: None,
method: "GET".to_string(),
path: "/users/{id}".to_string(),
parameters: vec![],
request_body: None,
responses: vec![],
security_requirements: vec![],
tags: vec!["users".to_string()],
deprecated: false,
external_docs_url: None,
examples: vec![],
display_group: None,
display_name: None,
aliases: vec!["lookup".to_string()],
hidden: false,
pagination: PaginationInfo::default(),
}],
base_url: Some("https://api.example.com".to_string()),
servers: vec![],
security_schemes: HashMap::new(),
skipped_endpoints: vec![],
server_variables: HashMap::new(),
};
let searcher = CommandSearcher::new();
let mut specs = BTreeMap::new();
specs.insert("mapped-api".to_string(), spec);
let results = searcher.search(&specs, "lookup", None).unwrap();
assert!(!results.is_empty(), "Should find command by alias");
assert_eq!(results[0].command.operation_id, "getUserById");
}
#[test]
fn test_search_suggestions_include_display_names() {
let spec = CachedSpec {
cache_format_version: CACHE_FORMAT_VERSION,
name: "mapped-api".to_string(),
version: "1.0.0".to_string(),
commands: vec![CachedCommand {
operation_id: "getUserById".to_string(),
name: "users".to_string(),
description: Some("Fetch a user".to_string()),
summary: None,
method: "GET".to_string(),
path: "/users/{id}".to_string(),
parameters: vec![],
request_body: None,
responses: vec![],
security_requirements: vec![],
tags: vec!["users".to_string()],
deprecated: false,
external_docs_url: None,
examples: vec![],
display_group: Some("accounts".to_string()),
display_name: Some("fetch".to_string()),
aliases: vec!["show".to_string()],
hidden: false,
pagination: PaginationInfo::default(),
}],
base_url: Some("https://api.example.com".to_string()),
servers: vec![],
security_schemes: HashMap::new(),
skipped_endpoints: vec![],
server_variables: HashMap::new(),
};
let searcher = CommandSearcher::new();
let suggestions = searcher.find_similar_commands(&spec, "fetc", 5);
assert!(
!suggestions.is_empty(),
"Should suggest command by display name prefix"
);
let (suggestion, _score) = &suggestions[0];
assert!(
suggestion.contains("accounts") && suggestion.contains("fetch"),
"Suggestion should use display group and name, got: {suggestions:?}"
);
}