use webpage_quality_analyzer::{
async_runtime::DefaultRuntime, Analyzer, FieldSelector, FieldSelectorBuilder,
};
const TEST_HTML: &str = r#"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="This is a test page for output customization testing with comprehensive metadata.">
<title>Test Page for Output Customization - Phase 6</title>
</head>
<body>
<header>
<h1>Main Title for Testing</h1>
<nav>
<a href="/page1">Link 1</a>
<a href="/page2">Link 2</a>
</nav>
</header>
<main>
<article>
<h2>Section 1: Introduction</h2>
<p>This is the first paragraph with some meaningful content for testing purposes. It contains enough words to pass basic content length checks.</p>
<p>This is the second paragraph that adds more substance to the content. We want to ensure there's sufficient content for analysis.</p>
<h2>Section 2: Details</h2>
<p>More detailed content goes here. This section provides additional information and ensures we have good structural depth with proper heading hierarchy.</p>
<ul>
<li>List item 1</li>
<li>List item 2</li>
<li>List item 3</li>
</ul>
<h3>Subsection 2.1</h3>
<p>Even more detailed content in a subsection. This demonstrates good document structure with multiple heading levels for better organization.</p>
</article>
</main>
<footer>
<p>Footer content with copyright information.</p>
</footer>
</body>
</html>
"#;
#[tokio::test]
async fn test_run_with_fields_basic() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let result = analyzer
.run_with_fields("https://test.com", Some(TEST_HTML), vec!["url", "score"])
.await
.unwrap();
assert!(result.get("url").is_some());
assert!(result.get("score").is_some());
assert!(result.get("metrics").is_none());
assert!(result.get("metadata").is_none());
assert!(result.get("processed_document").is_none());
}
#[tokio::test]
async fn test_run_with_fields_score_and_verdict() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let result = analyzer
.run_with_fields(
"https://test.com",
Some(TEST_HTML),
vec!["score", "verdict"],
)
.await
.unwrap();
assert!(result.get("score").is_some());
assert!(result.get("verdict").is_some());
assert!(result.get("url").is_none());
assert!(result.get("metrics").is_none());
}
#[tokio::test]
async fn test_run_with_fields_multiple() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let result = analyzer
.run_with_fields(
"https://test.com",
Some(TEST_HTML),
vec!["url", "score", "verdict", "version"],
)
.await
.unwrap();
assert!(result.get("url").is_some());
assert!(result.get("score").is_some());
assert!(result.get("verdict").is_some());
assert!(result.get("version").is_some());
assert!(result.get("metrics").is_none());
}
#[tokio::test]
async fn test_run_with_selector_include_fields() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let selector = FieldSelectorBuilder::new()
.include_fields(vec![
"url".to_string(),
"score".to_string(),
"verdict".to_string(),
])
.build();
let result = analyzer
.run_with_selector("https://test.com", Some(TEST_HTML), &selector)
.await
.unwrap();
assert!(result.get("url").is_some());
assert!(result.get("score").is_some());
assert!(result.get("verdict").is_some());
assert!(result.get("metrics").is_none());
assert!(result.get("metadata").is_none());
}
#[tokio::test]
async fn test_run_with_selector_include_sections() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let selector = FieldSelectorBuilder::new()
.include_sections(vec!["metadata".to_string()])
.build();
let result = analyzer
.run_with_selector("https://test.com", Some(TEST_HTML), &selector)
.await
.unwrap();
assert!(result.get("metadata").is_some());
assert!(result.get("url").is_none());
assert!(result.get("score").is_none());
assert!(result.get("metrics").is_none());
}
#[tokio::test]
async fn test_run_with_selector_exclude_fields() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let selector = FieldSelectorBuilder::new()
.exclude_fields(vec![
"metadata".to_string(),
"processed_document".to_string(),
])
.build();
let result = analyzer
.run_with_selector("https://test.com", Some(TEST_HTML), &selector)
.await
.unwrap();
assert!(result.get("url").is_some());
assert!(result.get("score").is_some());
assert!(result.get("verdict").is_some());
assert!(result.get("metadata").is_none());
assert!(result.get("processed_document").is_none());
}
#[tokio::test]
async fn test_run_compact_basic() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let compact_json = analyzer
.run_compact("https://test.com", Some(TEST_HTML))
.await
.unwrap();
let parsed: serde_json::Value = serde_json::from_str(&compact_json).unwrap();
assert!(parsed.get("url").is_some());
assert!(parsed.get("score").is_some());
assert!(parsed.get("verdict").is_some());
}
#[tokio::test]
async fn test_run_compact_vs_regular() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let compact_json = analyzer
.run_compact("https://test.com", Some(TEST_HTML))
.await
.unwrap();
let regular_report = analyzer
.run("https://test.com", Some(TEST_HTML))
.await
.unwrap();
let regular_json = regular_report.to_readable_json().unwrap();
let compact_parsed: serde_json::Value = serde_json::from_str(&compact_json).unwrap();
let regular_parsed: serde_json::Value = serde_json::from_str(®ular_json).unwrap();
assert_eq!(
compact_parsed["score"].as_f64().unwrap(),
regular_parsed["score"].as_f64().unwrap()
);
assert!(compact_json.len() < regular_json.len());
}
#[tokio::test]
async fn test_run_compact_size_reduction() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let compact_json = analyzer
.run_compact("https://test.com", Some(TEST_HTML))
.await
.unwrap();
let newline_count = compact_json.matches('\n').count();
assert!(
newline_count < 10,
"Compact JSON has {} newlines, expected < 10",
newline_count
);
}
#[tokio::test]
async fn test_selector_with_metrics_section() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let selector = FieldSelectorBuilder::new()
.include_sections(vec!["metrics".to_string()])
.build();
let result = analyzer
.run_with_selector("https://test.com", Some(TEST_HTML), &selector)
.await
.unwrap();
assert!(result.get("metrics").is_some());
let metrics = result.get("metrics").unwrap();
assert!(metrics.get("html_analysis").is_some() || metrics.get("network_metrics").is_some());
}
#[tokio::test]
async fn test_selector_url_score_metrics() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let selector = FieldSelectorBuilder::new()
.include_fields(vec!["url".to_string(), "score".to_string()])
.include_sections(vec!["metrics".to_string()])
.build();
let result = analyzer
.run_with_selector("https://test.com", Some(TEST_HTML), &selector)
.await
.unwrap();
assert!(result.get("url").is_some());
assert!(result.get("score").is_some());
assert!(result.get("metrics").is_some());
assert!(result.get("verdict").is_none());
assert!(result.get("metadata").is_none());
}
#[tokio::test]
async fn test_empty_selector() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let selector = FieldSelectorBuilder::new().build();
let result = analyzer
.run_with_selector("https://test.com", Some(TEST_HTML), &selector)
.await
.unwrap();
assert!(result.get("url").is_some());
assert!(result.get("score").is_some());
assert!(result.get("verdict").is_some());
assert!(result.get("metrics").is_some());
}
#[tokio::test]
async fn test_output_customization_with_profile() {
let analyzer = Analyzer::<DefaultRuntime>::builder()
.with_profile_name("news")
.unwrap()
.build()
.unwrap();
let result = analyzer
.run_with_fields(
"https://test.com",
Some(TEST_HTML),
vec!["score", "verdict", "version"],
)
.await
.unwrap();
assert!(result.get("score").is_some());
assert!(result.get("verdict").is_some());
assert!(result.get("version").is_some());
let version = result["version"].as_str().unwrap();
assert!(version.contains("news") || version.contains("v0."));
}
#[tokio::test]
async fn test_compact_with_disabled_metrics() {
let analyzer = Analyzer::<DefaultRuntime>::builder()
.disable_metric("paragraph_count")
.unwrap()
.build()
.unwrap();
let compact_json = analyzer
.run_compact("https://test.com", Some(TEST_HTML))
.await
.unwrap();
let parsed: serde_json::Value = serde_json::from_str(&compact_json).unwrap();
assert!(parsed.get("score").is_some());
assert!(parsed.get("verdict").is_some());
}
#[tokio::test]
async fn test_selector_with_custom_thresholds() {
let analyzer = Analyzer::<DefaultRuntime>::builder()
.set_metric_threshold("word_count", 100.0, 500.0, 2000.0, 5000.0)
.unwrap()
.build()
.unwrap();
let result = analyzer
.run_with_fields(
"https://test.com",
Some(TEST_HTML),
vec!["score", "metrics"],
)
.await
.unwrap();
assert!(result.get("score").is_some());
assert!(result.get("metrics").is_some());
}
#[tokio::test]
async fn test_run_with_fields_empty_list() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let result = analyzer
.run_with_fields("https://test.com", Some(TEST_HTML), vec![])
.await
.unwrap();
assert!(result.is_object());
}
#[tokio::test]
async fn test_run_with_fields_nonexistent_field() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let result = analyzer
.run_with_fields(
"https://test.com",
Some(TEST_HTML),
vec!["nonexistent_field"],
)
.await
.unwrap();
assert!(result.is_object());
assert!(result.get("nonexistent_field").is_none());
}
#[tokio::test]
async fn test_selector_exclude_all_then_include_one() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let selector = FieldSelectorBuilder::new()
.exclude_fields(vec![
"metadata".to_string(),
"processed_document".to_string(),
"notes".to_string(),
])
.include_fields(vec!["score".to_string()])
.build();
let result = analyzer
.run_with_selector("https://test.com", Some(TEST_HTML), &selector)
.await
.unwrap();
assert!(result.get("score").is_some());
assert!(result.get("metadata").is_none());
assert!(result.get("processed_document").is_none());
}
#[tokio::test]
async fn test_field_selection_reduces_size() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let full_report = analyzer
.run("https://test.com", Some(TEST_HTML))
.await
.unwrap();
let full_json = serde_json::to_string(&full_report).unwrap();
let filtered = analyzer
.run_with_fields(
"https://test.com",
Some(TEST_HTML),
vec!["url", "score", "verdict"],
)
.await
.unwrap();
let filtered_json = serde_json::to_string(&filtered).unwrap();
assert!(
filtered_json.len() < full_json.len() / 2,
"Filtered JSON ({} bytes) should be less than half of full JSON ({} bytes)",
filtered_json.len(),
full_json.len()
);
}
#[tokio::test]
async fn test_compact_vs_pretty_size() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let compact = analyzer
.run_compact("https://test.com", Some(TEST_HTML))
.await
.unwrap();
let report = analyzer
.run("https://test.com", Some(TEST_HTML))
.await
.unwrap();
let pretty = report.to_readable_json().unwrap();
assert!(
compact.len() < pretty.len(),
"Compact ({} bytes) should be smaller than pretty ({} bytes)",
compact.len(),
pretty.len()
);
}
#[tokio::test]
async fn test_minimal_output_combination() {
let analyzer = Analyzer::<DefaultRuntime>::builder().build().unwrap();
let selector = FieldSelectorBuilder::new()
.include_fields(vec!["url".to_string(), "score".to_string()])
.build();
let result = analyzer
.run_with_selector("https://test.com", Some(TEST_HTML), &selector)
.await
.unwrap();
let compact_json = serde_json::to_string(&result).unwrap();
assert!(
compact_json.len() < 200,
"Minimal output should be very small, got {} bytes",
compact_json.len()
);
}
#[tokio::test]
async fn test_output_customization_comprehensive() {
let analyzer = Analyzer::<DefaultRuntime>::builder()
.with_profile_name("content_article")
.unwrap()
.disable_metric("paragraph_count")
.unwrap()
.set_metric_threshold("word_count", 200.0, 800.0, 2500.0, 10000.0)
.unwrap()
.set_metric_weight("word_count", 1.5)
.unwrap()
.build()
.unwrap();
let fields_result = analyzer
.run_with_fields(
"https://test.com",
Some(TEST_HTML),
vec!["score", "verdict"],
)
.await
.unwrap();
assert!(fields_result.get("score").is_some());
assert!(fields_result.get("verdict").is_some());
let selector = FieldSelectorBuilder::new()
.include_fields(vec!["url".to_string(), "score".to_string()])
.build();
let selector_result = analyzer
.run_with_selector("https://test.com", Some(TEST_HTML), &selector)
.await
.unwrap();
assert!(selector_result.get("url").is_some());
assert!(selector_result.get("score").is_some());
let compact = analyzer
.run_compact("https://test.com", Some(TEST_HTML))
.await
.unwrap();
let compact_parsed: serde_json::Value = serde_json::from_str(&compact).unwrap();
assert!(compact_parsed.get("score").is_some());
let fields_score = fields_result["score"].as_f64().unwrap();
let selector_score = selector_result["score"].as_f64().unwrap();
let compact_score = compact_parsed["score"].as_f64().unwrap();
assert!((fields_score - selector_score).abs() < 0.01);
assert!((fields_score - compact_score).abs() < 0.01);
}