199 lines
5.6 KiB
Rust
199 lines
5.6 KiB
Rust
|
|
use chrono::Utc;
|
||
|
|
use opensearch::http::transport::Transport;
|
||
|
|
use opensearch::OpenSearch;
|
||
|
|
use pretty_assertions::assert_eq;
|
||
|
|
use serde_json::json;
|
||
|
|
use uuid::Uuid;
|
||
|
|
use wfe_core::models::{WorkflowInstance, WorkflowStatus};
|
||
|
|
use wfe_core::traits::search::{SearchFilter, SearchIndex};
|
||
|
|
use wfe_opensearch::OpenSearchIndex;
|
||
|
|
|
||
|
|
const OPENSEARCH_URL: &str = "http://localhost:9200";
|
||
|
|
|
||
|
|
/// Check if OpenSearch is reachable, skip test if not.
|
||
|
|
async fn opensearch_available() -> bool {
|
||
|
|
let transport = Transport::single_node(OPENSEARCH_URL);
|
||
|
|
if transport.is_err() {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
let client = OpenSearch::new(transport.unwrap());
|
||
|
|
client
|
||
|
|
.ping()
|
||
|
|
.send()
|
||
|
|
.await
|
||
|
|
.map(|r| r.status_code().is_success())
|
||
|
|
.unwrap_or(false)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Helper to create a unique test index and return the provider + cleanup handle.
|
||
|
|
async fn setup() -> Option<(OpenSearchIndex, String)> {
|
||
|
|
if !opensearch_available().await {
|
||
|
|
eprintln!("OpenSearch not available, skipping test");
|
||
|
|
return None;
|
||
|
|
}
|
||
|
|
let index_name = format!("wfe_test_{}", Uuid::new_v4());
|
||
|
|
let provider = OpenSearchIndex::new(OPENSEARCH_URL, &index_name).unwrap();
|
||
|
|
provider.start().await.unwrap();
|
||
|
|
Some((provider, index_name))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Refresh the index so documents become searchable immediately.
|
||
|
|
async fn refresh_index(provider: &OpenSearchIndex) {
|
||
|
|
let url = format!("/{}/_refresh", provider.index_name());
|
||
|
|
provider
|
||
|
|
.client()
|
||
|
|
.send(
|
||
|
|
opensearch::http::Method::Post,
|
||
|
|
&url,
|
||
|
|
opensearch::http::headers::HeaderMap::new(),
|
||
|
|
Option::<&serde_json::Value>::None,
|
||
|
|
Some(b"".as_ref()),
|
||
|
|
None,
|
||
|
|
)
|
||
|
|
.await
|
||
|
|
.unwrap();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Delete the test index.
|
||
|
|
async fn cleanup(provider: &OpenSearchIndex) {
|
||
|
|
let _ = provider
|
||
|
|
.client()
|
||
|
|
.indices()
|
||
|
|
.delete(opensearch::indices::IndicesDeleteParts::Index(&[
|
||
|
|
provider.index_name(),
|
||
|
|
]))
|
||
|
|
.send()
|
||
|
|
.await;
|
||
|
|
}
|
||
|
|
|
||
|
|
fn make_instance(description: Option<&str>, reference: Option<&str>) -> WorkflowInstance {
|
||
|
|
let mut instance = WorkflowInstance::new("test-workflow", 1, json!({"key": "value"}));
|
||
|
|
instance.description = description.map(String::from);
|
||
|
|
instance.reference = reference.map(String::from);
|
||
|
|
instance
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn index_and_search_by_terms() {
|
||
|
|
let Some((provider, _index)) = setup().await else {
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
|
||
|
|
let instance = make_instance(Some("Process the quarterly financial report"), None);
|
||
|
|
provider.index_workflow(&instance).await.unwrap();
|
||
|
|
refresh_index(&provider).await;
|
||
|
|
|
||
|
|
let page = provider
|
||
|
|
.search("quarterly financial", 0, 10, &[])
|
||
|
|
.await
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
assert_eq!(page.total, 1);
|
||
|
|
assert_eq!(page.data.len(), 1);
|
||
|
|
assert_eq!(page.data[0].id, instance.id);
|
||
|
|
assert_eq!(
|
||
|
|
page.data[0].description.as_deref(),
|
||
|
|
Some("Process the quarterly financial report")
|
||
|
|
);
|
||
|
|
|
||
|
|
cleanup(&provider).await;
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn search_with_status_filter() {
|
||
|
|
let Some((provider, _index)) = setup().await else {
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut runnable = make_instance(Some("Runnable workflow"), None);
|
||
|
|
runnable.status = WorkflowStatus::Runnable;
|
||
|
|
|
||
|
|
let mut complete = make_instance(Some("Complete workflow"), None);
|
||
|
|
complete.status = WorkflowStatus::Complete;
|
||
|
|
complete.complete_time = Some(Utc::now());
|
||
|
|
|
||
|
|
provider.index_workflow(&runnable).await.unwrap();
|
||
|
|
provider.index_workflow(&complete).await.unwrap();
|
||
|
|
refresh_index(&provider).await;
|
||
|
|
|
||
|
|
let page = provider
|
||
|
|
.search("", 0, 10, &[SearchFilter::Status(WorkflowStatus::Complete)])
|
||
|
|
.await
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
assert_eq!(page.total, 1);
|
||
|
|
assert_eq!(page.data[0].id, complete.id);
|
||
|
|
assert_eq!(page.data[0].status, WorkflowStatus::Complete);
|
||
|
|
|
||
|
|
cleanup(&provider).await;
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn search_with_no_results() {
|
||
|
|
let Some((provider, _index)) = setup().await else {
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
|
||
|
|
let instance = make_instance(Some("A regular workflow"), None);
|
||
|
|
provider.index_workflow(&instance).await.unwrap();
|
||
|
|
refresh_index(&provider).await;
|
||
|
|
|
||
|
|
let page = provider
|
||
|
|
.search("nonexistent-xyzzy-42", 0, 10, &[])
|
||
|
|
.await
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
assert_eq!(page.total, 0);
|
||
|
|
assert!(page.data.is_empty());
|
||
|
|
|
||
|
|
cleanup(&provider).await;
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn index_multiple_and_paginate() {
|
||
|
|
let Some((provider, _index)) = setup().await else {
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut instances = Vec::new();
|
||
|
|
for i in 0..5 {
|
||
|
|
let instance = make_instance(Some(&format!("Paginated workflow number {i}")), None);
|
||
|
|
provider.index_workflow(&instance).await.unwrap();
|
||
|
|
instances.push(instance);
|
||
|
|
}
|
||
|
|
refresh_index(&provider).await;
|
||
|
|
|
||
|
|
// Search all, but skip 2 and take 2
|
||
|
|
let page = provider.search("Paginated workflow", 2, 2, &[]).await.unwrap();
|
||
|
|
|
||
|
|
assert_eq!(page.total, 5);
|
||
|
|
assert_eq!(page.data.len(), 2);
|
||
|
|
|
||
|
|
cleanup(&provider).await;
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn search_by_reference() {
|
||
|
|
let Some((provider, _index)) = setup().await else {
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
|
||
|
|
let inst1 = make_instance(Some("First workflow"), Some("REF-001"));
|
||
|
|
let inst2 = make_instance(Some("Second workflow"), Some("REF-002"));
|
||
|
|
|
||
|
|
provider.index_workflow(&inst1).await.unwrap();
|
||
|
|
provider.index_workflow(&inst2).await.unwrap();
|
||
|
|
refresh_index(&provider).await;
|
||
|
|
|
||
|
|
let page = provider
|
||
|
|
.search("", 0, 10, &[SearchFilter::Reference("REF-001".to_string())])
|
||
|
|
.await
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
assert_eq!(page.total, 1);
|
||
|
|
assert_eq!(page.data[0].id, inst1.id);
|
||
|
|
assert_eq!(page.data[0].reference.as_deref(), Some("REF-001"));
|
||
|
|
|
||
|
|
cleanup(&provider).await;
|
||
|
|
}
|