222 lines
7.4 KiB
Rust
222 lines
7.4 KiB
Rust
//! Unit test: Ingest pipeline with detailed error logging
|
|||
|
|
//!
|
||
|
|
//! Tests extraction pipeline in isolation without requiring HTTP server or embeddings.
|
||
|
|
//! Useful for debugging extraction errors.
|
||
|
|
//!
|
||
|
|
//! Usage:
|
||
|
|
//! ```
|
||
|
|
//! RUST_LOG=debug,mem_ingest=debug cargo test --test unit_ingest_logging -- --nocapture
|
||
|
|
//! ```
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use mem_ingest::ingest_pipeline::{IngestPipeline, Episode};
|
||
|
|
use mem_ingest::entity_extractor::WikiLinkFallbackExtractor;
|
||
|
|
use mem_ingest::fact_extractor::SimpleFactExtractor;
|
||
|
|
use mem_ingest::contradiction_detector::ContradictionHandler;
|
||
|
|
use std::sync::Arc;
|
||
|
|
|
||
|
|
fn init_logging() {
|
||
|
|
let _ = tracing_subscriber::fmt()
|
||
|
|
.with_max_level(tracing::Level::DEBUG)
|
||
|
|
.with_writer(std::io::stderr)
|
||
|
|
.try_init();
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_wiki_link_extraction() {
|
||
|
|
init_logging();
|
||
|
|
|
||
|
|
println!("\n[TEST] Wiki link extraction with logging\n");
|
||
|
|
|
||
|
|
let entity_extractor = Arc::new(WikiLinkFallbackExtractor);
|
||
|
|
let fact_extractor = Arc::new(SimpleFactExtractor);
|
||
|
|
let contradiction_detector = Arc::new(ContradictionHandler::default());
|
||
|
|
|
||
|
|
let pipeline = IngestPipeline::new(
|
||
|
|
entity_extractor,
|
||
|
|
fact_extractor,
|
||
|
|
contradiction_detector,
|
||
|
|
);
|
||
|
|
|
||
|
|
let episode = Episode {
|
||
|
|
id: "test-1".to_string(),
|
||
|
|
project_id: "test-project".to_string(),
|
||
|
|
text: "Kubernetes [[Docker]] is a [[Container]] orchestration platform. It works with [[Go]] programs."
|
||
|
|
.to_string(),
|
||
|
|
wiki_links: vec!["Docker".to_string(), "Container".to_string(), "Go".to_string()],
|
||
|
|
};
|
||
|
|
|
||
|
|
tracing::info!(
|
||
|
|
target: "test",
|
||
|
|
episode_id = %episode.id,
|
||
|
|
wiki_links = ?episode.wiki_links,
|
||
|
|
"Starting pipeline ingest"
|
||
|
|
);
|
||
|
|
|
||
|
|
match pipeline.ingest(&episode).await {
|
||
|
|
Ok(result) => {
|
||
|
|
tracing::info!(
|
||
|
|
target: "test",
|
||
|
|
entities = result.entities.len(),
|
||
|
|
edges = result.edges.len(),
|
||
|
|
reviews = result.reviews.len(),
|
||
|
|
"Pipeline succeeded"
|
||
|
|
);
|
||
|
|
|
||
|
|
println!("✓ Extracted {} entities", result.entities.len());
|
||
|
|
for entity in &result.entities {
|
||
|
|
println!(" - {} ({}): {}", entity.name, entity.entity_type.as_str(), entity.summary.as_deref().unwrap_or(""));
|
||
|
|
}
|
||
|
|
|
||
|
|
println!("✓ Extracted {} edges", result.edges.len());
|
||
|
|
for edge in &result.edges {
|
||
|
|
println!(" - {} --[{}]--> {}", edge.source_entity_id, edge.relation_type, edge.target_entity_id);
|
||
|
|
}
|
||
|
|
|
||
|
|
assert!(result.entities.len() > 0, "Should extract entities");
|
||
|
|
}
|
||
|
|
Err(e) => {
|
||
|
|
tracing::error!(
|
||
|
|
target: "test",
|
||
|
|
error = %e,
|
||
|
|
"Pipeline failed"
|
||
|
|
);
|
||
|
|
panic!("Pipeline failed: {}", e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_extraction_error_logging() {
|
||
|
|
init_logging();
|
||
|
|
|
||
|
|
println!("\n[TEST] Pipeline error handling with logging\n");
|
||
|
|
|
||
|
|
let entity_extractor = Arc::new(WikiLinkFallbackExtractor);
|
||
|
|
let fact_extractor = Arc::new(SimpleFactExtractor);
|
||
|
|
let contradiction_detector = Arc::new(ContradictionHandler::default());
|
||
|
|
|
||
|
|
let pipeline = IngestPipeline::new(
|
||
|
|
entity_extractor,
|
||
|
|
fact_extractor,
|
||
|
|
contradiction_detector,
|
||
|
|
);
|
||
|
|
|
||
|
|
// Episode with problematic content (empty, or only whitespace)
|
||
|
|
let episode = Episode {
|
||
|
|
id: "test-empty".to_string(),
|
||
|
|
project_id: "test-project".to_string(),
|
||
|
|
text: "".to_string(),
|
||
|
|
wiki_links: vec![],
|
||
|
|
};
|
||
|
|
|
||
|
|
tracing::info!(
|
||
|
|
target: "test",
|
||
|
|
episode_id = %episode.id,
|
||
|
|
text_len = episode.text.len(),
|
||
|
|
"Processing empty episode"
|
||
|
|
);
|
||
|
|
|
||
|
|
match pipeline.ingest(&episode).await {
|
||
|
|
Ok(result) => {
|
||
|
|
tracing::info!(
|
||
|
|
target: "test",
|
||
|
|
entities = result.entities.len(),
|
||
|
|
edges = result.edges.len(),
|
||
|
|
"Empty episode processed (no error expected)"
|
||
|
|
);
|
||
|
|
println!("✓ Empty episode handled gracefully");
|
||
|
|
}
|
||
|
|
Err(e) => {
|
||
|
|
tracing::error!(
|
||
|
|
target: "test",
|
||
|
|
error = %e,
|
||
|
|
"Empty episode caused error"
|
||
|
|
);
|
||
|
|
// Empty is OK for some extractors
|
||
|
|
println!("⚠ Empty episode error (may be expected): {}", e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_multiple_records_error_accumulation() {
|
||
|
|
init_logging();
|
||
|
|
|
||
|
|
println!("\n[TEST] Processing multiple records and logging errors\n");
|
||
|
|
|
||
|
|
let entity_extractor = Arc::new(WikiLinkFallbackExtractor);
|
||
|
|
let fact_extractor = Arc::new(SimpleFactExtractor);
|
||
|
|
let contradiction_detector = Arc::new(ContradictionHandler::default());
|
||
|
|
|
||
|
|
let pipeline = IngestPipeline::new(
|
||
|
|
entity_extractor,
|
||
|
|
fact_extractor,
|
||
|
|
contradiction_detector,
|
||
|
|
);
|
||
|
|
|
||
|
|
let records = vec![
|
||
|
|
("Kubernetes [[Docker]] is a container orchestrator", "wiki/k8s"),
|
||
|
|
("Docker [[Linux]] containers enable microservices", "wiki/docker"),
|
||
|
|
("", "wiki/empty"),
|
||
|
|
("Go [[Concurrency]] is powerful for backend services", "wiki/go"),
|
||
|
|
];
|
||
|
|
|
||
|
|
let mut success_count = 0;
|
||
|
|
let mut error_count = 0;
|
||
|
|
|
||
|
|
for (idx, (text, source)) in records.iter().enumerate() {
|
||
|
|
let episode = Episode {
|
||
|
|
id: format!("record-{}", idx),
|
||
|
|
project_id: "test-project".to_string(),
|
||
|
|
text: text.to_string(),
|
||
|
|
wiki_links: vec![],
|
||
|
|
};
|
||
|
|
|
||
|
|
tracing::info!(
|
||
|
|
target: "test",
|
||
|
|
record_idx = idx,
|
||
|
|
source = source,
|
||
|
|
text_len = text.len(),
|
||
|
|
"Processing record"
|
||
|
|
);
|
||
|
|
|
||
|
|
match pipeline.ingest(&episode).await {
|
||
|
|
Ok(result) => {
|
||
|
|
tracing::debug!(
|
||
|
|
target: "test",
|
||
|
|
record_idx = idx,
|
||
|
|
entities = result.entities.len(),
|
||
|
|
edges = result.edges.len(),
|
||
|
|
"Record succeeded"
|
||
|
|
);
|
||
|
|
println!(" ✓ Record {}: {} entities, {} edges", idx, result.entities.len(), result.edges.len());
|
||
|
|
success_count += 1;
|
||
|
|
}
|
||
|
|
Err(e) => {
|
||
|
|
tracing::warn!(
|
||
|
|
target: "test",
|
||
|
|
record_idx = idx,
|
||
|
|
error = %e,
|
||
|
|
source = source,
|
||
|
|
"Record failed"
|
||
|
|
);
|
||
|
|
println!(" ✗ Record {}: {}", idx, e);
|
||
|
|
error_count += 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
println!("\nSummary: {} success, {} errors", success_count, error_count);
|
||
|
|
|
||
|
|
tracing::info!(
|
||
|
|
target: "test",
|
||
|
|
total_records = records.len(),
|
||
|
|
success = success_count,
|
||
|
|
errors = error_count,
|
||
|
|
"Batch processing complete"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|