fix(tests): update integration.rs for meilisearch_sdk v0.27 API
The meilisearch_sdk v0.27 API changes: - get_task() expects TaskInfo, not u32 - Client::new() returns Result<Client, Error> - search().execute() returns SearchResults<T> with SearchResult<T>.result field - with_facets() expects Selectors<&[&str]>, not &[&str] - set_synonyms() expects HashMap, not Value - number_of_documents returns usize, not Option<usize> Updated integration.rs to match the new API: - Use TaskInfo directly in wait_for_task() - Handle Client::new() Result return type - Access hits via SearchResult.result field - Use Selectors::Some() for facets - Use HashMap for synonyms - Fix lifetime issues with result access Fixes compilation errors in integration test suite. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
94a5daa319
commit
0c3cdee8c6
1 changed files with 65 additions and 63 deletions
|
|
@ -12,7 +12,7 @@
|
|||
// Run:
|
||||
// cargo test --test integration -- --test-threads=1
|
||||
|
||||
use meilisearch_sdk::{client::Client, indexes::Index, tasks::Task};
|
||||
use meilisearch_sdk::{client::Client, indexes::Index, search::SearchResults, tasks::Task};
|
||||
use reqwest::StatusCode;
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
|
|
@ -41,25 +41,32 @@ struct TestDoc {
|
|||
/// Helper: Get Miroir client
|
||||
fn miroir_client() -> Client {
|
||||
let url = format!("http://localhost:{}", MIROIR_PORT);
|
||||
Client::new(url, Some(MASTER_KEY.to_string()))
|
||||
Client::new(url, Some(MASTER_KEY.to_string())).expect("Failed to create Miroir client")
|
||||
}
|
||||
|
||||
/// Helper: Get direct client to a Meilisearch node
|
||||
fn node_client(port: u16) -> Client {
|
||||
let url = format!("http://localhost:{}", port);
|
||||
Client::new(url, Some(NODE_KEY.to_string()))
|
||||
Client::new(url, Some(NODE_KEY.to_string())).expect("Failed to create Meilisearch node client")
|
||||
}
|
||||
|
||||
/// Helper: Wait for a task to complete
|
||||
async fn wait_for_task(client: &Client, task_uid: u32) -> Result<Task, Box<dyn std::error::Error>> {
|
||||
async fn wait_for_task(
|
||||
client: &Client,
|
||||
task_info: meilisearch_sdk::task_info::TaskInfo,
|
||||
) -> Result<Task, Box<dyn std::error::Error>> {
|
||||
let timeout = Duration::from_secs(30);
|
||||
let start = std::time::Instant::now();
|
||||
let task_uid = task_info.task_uid;
|
||||
|
||||
loop {
|
||||
let task = client.get_task(&task_uid).await?;
|
||||
let task = client.get_task(&task_info).await?;
|
||||
// Check if task is finished (Succeeded or Failed)
|
||||
match task {
|
||||
Task::Succeeded { .. } | Task::Failed { .. } => return Ok(task),
|
||||
Task::Succeeded { .. } => return Ok(task),
|
||||
Task::Failed { .. } => {
|
||||
return Err(format!("Task {} failed: {:?}", task_uid, task).into())
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
@ -73,23 +80,21 @@ async fn wait_for_task(client: &Client, task_uid: u32) -> Result<Task, Box<dyn s
|
|||
|
||||
/// Helper: Create or get index with primary key
|
||||
async fn get_index(client: &Client, name: &str) -> Result<Index, Box<dyn std::error::Error>> {
|
||||
let indexes = client.clone();
|
||||
match indexes.get_index(name).await {
|
||||
Ok(_) => Ok(indexes),
|
||||
match client.get_index(name).await {
|
||||
Ok(_) => Ok(client.index(name)),
|
||||
Err(_) => {
|
||||
let task = indexes.create_index(name, Some("id")).await?;
|
||||
wait_for_task(&indexes, task).await?;
|
||||
Ok(indexes)
|
||||
let task_info = client.create_index(name, Some("id")).await?;
|
||||
wait_for_task(client, task_info).await?;
|
||||
Ok(client.index(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper: Delete index if exists
|
||||
async fn delete_index(client: &Client, name: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let indexes = client.clone();
|
||||
if indexes.get_index(name).await.is_ok() {
|
||||
let task = indexes.delete_index(name).await?;
|
||||
let _ = wait_for_task(&indexes, task).await;
|
||||
if client.get_index(name).await.is_ok() {
|
||||
let task_info = client.delete_index(name).await?;
|
||||
let _ = wait_for_task(client, task_info).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -150,7 +155,7 @@ async fn document_round_trip() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let node = node_client(port);
|
||||
if let Ok(idx) = node.get_index(index_name).await {
|
||||
let stats = idx.get_stats().await?;
|
||||
let count = stats.number_of_documents.unwrap_or(0);
|
||||
let count = stats.number_of_documents;
|
||||
node_doc_counts.insert(port, count);
|
||||
}
|
||||
}
|
||||
|
|
@ -164,7 +169,7 @@ async fn document_round_trip() -> Result<(), Box<dyn std::error::Error>> {
|
|||
);
|
||||
|
||||
// Total across nodes equals 1000
|
||||
let total: u64 = node_doc_counts.values().sum();
|
||||
let total: usize = node_doc_counts.values().sum();
|
||||
assert_eq!(
|
||||
total, 1000,
|
||||
"Total documents mismatch: {:?}",
|
||||
|
|
@ -206,8 +211,8 @@ async fn search_covers_all_shards() -> Result<(), Box<dyn std::error::Error>> {
|
|||
// Search for each unique keyword — every search must return exactly 1 hit
|
||||
for i in 0..100 {
|
||||
let keyword = format!("unique_keyword_{}", i);
|
||||
let results: Value = index.search().with_query(&keyword).execute().await?;
|
||||
let hits = results["hits"].as_array().unwrap();
|
||||
let results: SearchResults<Value> = index.search().with_query(&keyword).execute().await?;
|
||||
let hits = results.hits;
|
||||
assert_eq!(
|
||||
hits.len(),
|
||||
1,
|
||||
|
|
@ -255,15 +260,18 @@ async fn facet_aggregation() -> Result<(), Box<dyn std::error::Error>> {
|
|||
wait_for_task(&client, task).await?;
|
||||
|
||||
// Facet counts must sum to 100
|
||||
let results: Value = index.search().with_facet(["color"]).execute().await?;
|
||||
let facet_dist = results["facetDistribution"]["color"].as_object().unwrap();
|
||||
use meilisearch_sdk::search::Selectors;
|
||||
let facets = ["color"];
|
||||
let results: SearchResults<Value> = index.search().with_facets(Selectors::Some(&facets[..])).execute().await?;
|
||||
let facet_dist = results
|
||||
.facet_distribution
|
||||
.as_ref()
|
||||
.and_then(|f| f.get("color"))
|
||||
.unwrap();
|
||||
|
||||
let red_count = facet_dist.get("red").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
let green_count = facet_dist
|
||||
.get("green")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(0);
|
||||
let blue_count = facet_dist.get("blue").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
let red_count = *facet_dist.get("red").unwrap_or(&0);
|
||||
let green_count = *facet_dist.get("green").unwrap_or(&0);
|
||||
let blue_count = *facet_dist.get("blue").unwrap_or(&0);
|
||||
|
||||
let total = red_count + green_count + blue_count;
|
||||
assert_eq!(total, 100, "Facet counts sum to {}, expected 100", total);
|
||||
|
|
@ -305,27 +313,24 @@ async fn offset_limit_paging() -> Result<(), Box<dyn std::error::Error>> {
|
|||
wait_for_task(&client, task).await?;
|
||||
|
||||
// Get single query with limit=50
|
||||
let single_page: Value = index.search().with_limit(50).execute().await?;
|
||||
let single_ids: HashSet<String> = single_page["hits"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
let single_page: SearchResults<Value> = index.search().with_limit(50).execute().await?;
|
||||
let single_ids: HashSet<String> = single_page
|
||||
.hits
|
||||
.iter()
|
||||
.filter_map(|v| v["id"].as_str())
|
||||
.map(|s| s.to_string())
|
||||
.filter_map(|v| v.result.get("id").and_then(|id| id.as_str().map(|s| s.to_string())))
|
||||
.collect();
|
||||
|
||||
// Get 5 pages of 10
|
||||
let mut paged_ids = HashSet::new();
|
||||
for page in 0..5 {
|
||||
let results: Value = index
|
||||
let results: SearchResults<Value> = index
|
||||
.search()
|
||||
.with_limit(10)
|
||||
.with_offset(page * 10)
|
||||
.execute()
|
||||
.await?;
|
||||
let hits = results["hits"].as_array().unwrap();
|
||||
for hit in hits {
|
||||
let id = hit["id"].as_str().unwrap();
|
||||
for hit in results.hits {
|
||||
let id = hit.result.get("id").and_then(|id| id.as_str()).unwrap();
|
||||
paged_ids.insert(id.to_string());
|
||||
}
|
||||
}
|
||||
|
|
@ -344,23 +349,23 @@ async fn offset_limit_paging() -> Result<(), Box<dyn std::error::Error>> {
|
|||
);
|
||||
|
||||
// Order is preserved (concatenated pages match single page order)
|
||||
let single_order: Vec<_> = single_page["hits"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
let single_order: Vec<String> = single_page
|
||||
.hits
|
||||
.iter()
|
||||
.filter_map(|v| v["id"].as_str())
|
||||
.filter_map(|v| v.result.get("id").and_then(|id| id.as_str().map(|s| s.to_string())))
|
||||
.collect();
|
||||
|
||||
let mut paged_order = Vec::new();
|
||||
for page in 0..5 {
|
||||
let results: Value = index
|
||||
let results: SearchResults<Value> = index
|
||||
.search()
|
||||
.with_limit(10)
|
||||
.with_offset(page * 10)
|
||||
.execute()
|
||||
.await?;
|
||||
for hit in results["hits"].as_array().unwrap() {
|
||||
paged_order.push(hit["id"].as_str().unwrap());
|
||||
for hit in results.hits {
|
||||
let id = hit.result.get("id").and_then(|id| id.as_str()).unwrap();
|
||||
paged_order.push(id.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -397,13 +402,12 @@ async fn settings_broadcast() -> Result<(), Box<dyn std::error::Error>> {
|
|||
wait_for_task(&client, task).await?;
|
||||
|
||||
// Add synonyms via Miroir
|
||||
let synonyms = json!({
|
||||
"earbuds": ["headphones"],
|
||||
"wireless": ["bluetooth"]
|
||||
});
|
||||
let mut synonyms = HashMap::new();
|
||||
synonyms.insert("earbuds".to_string(), vec!["headphones".to_string()]);
|
||||
synonyms.insert("wireless".to_string(), vec!["bluetooth".to_string()]);
|
||||
|
||||
let task = index.set_synonyms(&synonyms).await?;
|
||||
wait_for_task(&client, task).await?;
|
||||
let task_info = index.set_synonyms(&synonyms).await?;
|
||||
wait_for_task(&client, task_info).await?;
|
||||
|
||||
// Verify all 3 nodes have the synonyms
|
||||
for &port in &NODE_PORTS {
|
||||
|
|
@ -421,13 +425,12 @@ async fn settings_broadcast() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
|
||||
// Search via synonym returns results
|
||||
let results: Value = index
|
||||
let results: SearchResults<Value> = index
|
||||
.search()
|
||||
.with_query("bluetooth headphones")
|
||||
.execute()
|
||||
.await?;
|
||||
let hits = results["hits"].as_array().unwrap();
|
||||
assert!(hits.len() >= 1, "Synonym search returned no results");
|
||||
assert!(results.hits.len() >= 1, "Synonym search returned no results");
|
||||
|
||||
delete_index(&client, index_name).await?;
|
||||
|
||||
|
|
@ -475,18 +478,17 @@ async fn task_polling() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
|
||||
// Search also returns all documents
|
||||
let results: Value = index
|
||||
let results: SearchResults<Value> = index
|
||||
.search()
|
||||
.with_query("content")
|
||||
.with_limit(500)
|
||||
.execute()
|
||||
.await?;
|
||||
let hits = results["hits"].as_array().unwrap();
|
||||
assert_eq!(
|
||||
hits.len(),
|
||||
results.hits.len(),
|
||||
500,
|
||||
"Search returned {} hits, expected 500",
|
||||
hits.len()
|
||||
results.hits.len()
|
||||
);
|
||||
|
||||
delete_index(&client, index_name).await?;
|
||||
|
|
@ -508,8 +510,9 @@ async fn node_failure_rf2() -> Result<(), Box<dyn std::error::Error>> {
|
|||
.unwrap_or(7700);
|
||||
|
||||
let client_url = format!("http://localhost:{}", rf2_port);
|
||||
let client = Client::new(client_url, Some(MASTER_KEY.to_string()));
|
||||
let index_name = "test_rf2_failure";
|
||||
let client = Client::new(&client_url, Some(MASTER_KEY.to_string()))
|
||||
.expect("Failed to create Meilisearch client");
|
||||
|
||||
delete_index(&client, index_name).await?;
|
||||
let index = get_index(&client, index_name).await?;
|
||||
|
|
@ -529,18 +532,17 @@ async fn node_failure_rf2() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
// Simulate stopping one node (in real test, use docker-compose stop)
|
||||
// For now, we'll just verify the search returns all results
|
||||
let results: Value = index
|
||||
let results: SearchResults<Value> = index
|
||||
.search()
|
||||
.with_query("content")
|
||||
.with_limit(500)
|
||||
.execute()
|
||||
.await?;
|
||||
let hits = results["hits"].as_array().unwrap();
|
||||
assert_eq!(
|
||||
hits.len(),
|
||||
results.hits.len(),
|
||||
500,
|
||||
"Search returned {} hits, expected 500",
|
||||
hits.len()
|
||||
results.hits.len()
|
||||
);
|
||||
|
||||
// Check for X-Miroir-Degraded header (should not appear with RF=2 when one node fails)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue