use std::collections::HashMap;
use webpage_quality_analyzer::config::profile_builder::ProfileBuilder;
use webpage_quality_analyzer::{analyze_with_profile, Analyzer, ConfigManager};
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">
<title>Professional Web Development Services - Expert Solutions</title>
<meta name="description" content="We provide professional web development services including responsive design, SEO optimization, and performance tuning for businesses of all sizes.">
<meta property="og:title" content="Web Development Services">
<meta property="og:description" content="Professional web solutions">
<meta property="og:image" content="https://example.com/og-image.jpg">
<meta property="og:url" content="https://example.com">
<meta name="twitter:card" content="summary_large_image">
<link rel="canonical" href="https://example.com">
</head>
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/services">Services</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main>
<h1>Professional Web Development Services</h1>
<section>
<h2>Our Expertise</h2>
<p>We are a team of experienced web developers specializing in creating high-performance, accessible, and SEO-friendly websites. Our comprehensive services cover every aspect of modern web development, from initial design to final deployment and ongoing maintenance.</p>
<img src="team.jpg" alt="Our professional web development team">
<h3>Full-Stack Development</h3>
<p>Our full-stack developers work with cutting-edge technologies to build robust web applications. We use modern frameworks and best practices to ensure your website is fast, secure, and scalable. From database design to frontend implementation, we handle it all.</p>
<h3>Responsive Design</h3>
<p>Every website we create is fully responsive and mobile-friendly. We follow mobile-first design principles to ensure your site looks and works perfectly on all devices, from smartphones to desktop computers. User experience is our top priority.</p>
<img src="responsive.jpg" alt="Responsive web design demonstration">
</section>
<section>
<h2>SEO Optimization</h2>
<p>Our SEO experts ensure your website ranks well in search engines. We implement best practices for on-page SEO, technical SEO, and content optimization to help you reach your target audience effectively.</p>
</section>
<section>
<h2>Performance Tuning</h2>
<p>We optimize your website for speed and efficiency. Our performance tuning services include code optimization, caching strategies, and asset optimization to ensure fast load times.</p>
</section>
<section>
<h2>Contact Us</h2>
<p>Ready to start your project? Get in touch with us today!</p>
</section>
</main>
<footer>
<p>Email: contact@example.com | Phone: (555) 123-4567</p>
<p>© 2025 Web Development Services. All rights reserved.</p>
</footer>
</body>
</html>
"#;
async fn analyze_with_custom_profile(
html: &str,
profile: webpage_quality_analyzer::config::enhanced_models::EnhancedScoringProfile,
profile_name: &str,
) -> Result<webpage_quality_analyzer::PageQualityReport, webpage_quality_analyzer::AnalyzeError> {
let mut config_manager = ConfigManager::new();
config_manager
.add_profile(profile_name.to_string(), profile)
.map_err(|e| webpage_quality_analyzer::AnalyzeError::InvalidProfile(e))?;
config_manager
.set_active_profile(profile_name)
.map_err(|e| webpage_quality_analyzer::AnalyzeError::InvalidProfile(e))?;
let analyzer: Analyzer = Analyzer::builder()
.with_config_manager(config_manager)
.build_unchecked();
analyzer.run("https://example.com/test", Some(html)).await
}
#[tokio::test]
async fn test_category_weight_affects_score() {
let mut weights_content = HashMap::new();
weights_content.insert("content".to_string(), 0.80);
weights_content.insert("seo".to_string(), 0.20);
let profile_content = ProfileBuilder::new("content_heavy")
.with_category_weights(weights_content)
.build()
.expect("Failed to build profile");
let mut weights_seo = HashMap::new();
weights_seo.insert("content".to_string(), 0.20);
weights_seo.insert("seo".to_string(), 0.80);
let profile_seo = ProfileBuilder::new("seo_heavy")
.with_category_weights(weights_seo)
.build()
.expect("Failed to build profile");
let report_content = analyze_with_custom_profile(TEST_HTML, profile_content, "content_heavy")
.await
.expect("Failed to analyze with content profile");
let report_seo = analyze_with_custom_profile(TEST_HTML, profile_seo, "seo_heavy")
.await
.expect("Failed to analyze with SEO profile");
println!("Content-heavy (80/20) score: {}", report_content.score);
println!("SEO-heavy (20/80) score: {}", report_seo.score);
assert_ne!(
report_content.score as i32, report_seo.score as i32,
"Scores should differ with different category weights"
);
}
#[tokio::test]
async fn test_balanced_vs_unbalanced_weights() {
let mut weights_balanced = HashMap::new();
weights_balanced.insert("content".to_string(), 0.33);
weights_balanced.insert("seo".to_string(), 0.33);
weights_balanced.insert("structure".to_string(), 0.34);
let profile_balanced = ProfileBuilder::new("balanced")
.with_category_weights(weights_balanced)
.build()
.expect("Failed to build profile");
let mut weights_unbalanced = HashMap::new();
weights_unbalanced.insert("content".to_string(), 0.90);
weights_unbalanced.insert("seo".to_string(), 0.05);
weights_unbalanced.insert("structure".to_string(), 0.05);
let profile_unbalanced = ProfileBuilder::new("unbalanced")
.with_category_weights(weights_unbalanced)
.build()
.expect("Failed to build profile");
let report_balanced = analyze_with_custom_profile(TEST_HTML, profile_balanced, "balanced")
.await
.expect("Failed to analyze");
let report_unbalanced =
analyze_with_custom_profile(TEST_HTML, profile_unbalanced, "unbalanced")
.await
.expect("Failed to analyze");
println!("Balanced weights score: {}", report_balanced.score);
println!("Unbalanced weights score: {}", report_unbalanced.score);
assert_ne!(
report_balanced.score as i32, report_unbalanced.score as i32,
"Balanced and unbalanced weights should produce different scores"
);
}
#[tokio::test]
async fn test_metric_weight_within_category() {
let mut weights_equal = HashMap::new();
weights_equal.insert("content".to_string(), 1.0);
let profile_equal = ProfileBuilder::new("equal_metrics")
.with_category_weights(weights_equal.clone())
.with_metric_weight("word_count", 0.33)
.unwrap()
.with_metric_weight("readability_fk", 0.33)
.unwrap()
.with_metric_weight("main_text_ratio", 0.34)
.unwrap()
.build()
.expect("Failed to build profile");
let profile_word_heavy = ProfileBuilder::new("word_heavy")
.with_category_weights(weights_equal)
.with_metric_weight("word_count", 0.80)
.unwrap()
.with_metric_weight("readability_fk", 0.10)
.unwrap()
.with_metric_weight("main_text_ratio", 0.10)
.unwrap()
.build()
.expect("Failed to build profile");
let report_equal = analyze_with_custom_profile(TEST_HTML, profile_equal, "equal_metrics")
.await
.expect("Failed to analyze");
let report_word_heavy =
analyze_with_custom_profile(TEST_HTML, profile_word_heavy, "word_heavy")
.await
.expect("Failed to analyze");
println!("Equal metric weights score: {}", report_equal.score);
println!("Word-count heavy score: {}", report_word_heavy.score);
assert!(report_equal.score > 0.0);
assert!(report_word_heavy.score > 0.0);
}
#[tokio::test]
async fn test_progressive_weight_changes() {
let weight_values = vec![0.10, 0.30, 0.50, 0.70, 0.90];
for content_weight in weight_values {
let seo_weight = 1.0 - content_weight;
let mut weights = HashMap::new();
weights.insert("content".to_string(), content_weight);
weights.insert("seo".to_string(), seo_weight);
let profile =
ProfileBuilder::new(&format!("progressive_{}", (content_weight * 100.0) as i32))
.with_category_weights(weights)
.build()
.expect("Failed to build profile");
let report = analyze_with_custom_profile(
TEST_HTML,
profile,
&format!("progressive_{}", (content_weight * 100.0) as i32),
)
.await
.expect("Failed to analyze");
println!(
"Content: {:.0}%, SEO: {:.0}% => Score: {:.2}",
content_weight * 100.0,
seo_weight * 100.0,
report.score
);
}
}
#[tokio::test]
async fn test_extreme_weights() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 0.99);
weights.insert("seo".to_string(), 0.01);
let profile = ProfileBuilder::new("extreme_weights")
.with_category_weights(weights)
.build()
.expect("Failed to build profile");
let report = analyze_with_custom_profile(TEST_HTML, profile, "extreme_weights")
.await
.expect("Failed to analyze");
println!("Extreme weights (99/1) score: {}", report.score);
assert!(report.score > 0.0, "Score should be positive");
}
#[tokio::test]
async fn test_zero_weight_category() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
weights.insert("seo".to_string(), 0.0);
let profile = ProfileBuilder::new("zero_seo")
.with_category_weights(weights)
.build()
.expect("Failed to build profile");
let report = analyze_with_custom_profile(TEST_HTML, profile, "zero_seo")
.await
.expect("Failed to analyze");
println!("Zero SEO weight score: {}", report.score);
assert!(report.score > 0.0);
}
#[tokio::test]
async fn test_multiple_category_weights() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 0.30);
weights.insert("seo".to_string(), 0.25);
weights.insert("structure".to_string(), 0.20);
weights.insert("media".to_string(), 0.15);
weights.insert("accessibility".to_string(), 0.10);
let profile = ProfileBuilder::new("five_categories")
.with_category_weights(weights)
.build()
.expect("Failed to build profile");
let total_weight: f32 = profile.category_weights.values().sum();
assert!(
(total_weight - 1.0).abs() < 0.001,
"Weights should sum to 1.0"
);
let report = analyze_with_custom_profile(TEST_HTML, profile, "five_categories")
.await
.expect("Failed to analyze");
println!("Five-category profile score: {}", report.score);
}
#[tokio::test]
async fn test_weight_validation() {
let mut valid_weights = HashMap::new();
valid_weights.insert("content".to_string(), 0.60);
valid_weights.insert("seo".to_string(), 0.40);
let valid_profile = ProfileBuilder::new("valid_weights")
.with_category_weights(valid_weights)
.build();
assert!(
valid_profile.is_ok(),
"Valid weights should build successfully"
);
let profile = valid_profile.unwrap();
let total: f32 = profile.category_weights.values().sum();
assert!(
(total - 1.0).abs() < 0.001,
"Total weight should be 1.0, got {}",
total
);
}
#[tokio::test]
async fn test_metric_weight_proportional_impact() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
let profile_low_weight = ProfileBuilder::new("low_word_weight")
.with_category_weights(weights.clone())
.with_metric_weight("word_count", 0.20)
.unwrap()
.build()
.expect("Failed to build profile");
let profile_high_weight = ProfileBuilder::new("high_word_weight")
.with_category_weights(weights)
.with_metric_weight("word_count", 0.80)
.unwrap()
.build()
.expect("Failed to build profile");
let report_low = analyze_with_custom_profile(TEST_HTML, profile_low_weight, "low_word_weight")
.await
.expect("Failed to analyze");
let report_high =
analyze_with_custom_profile(TEST_HTML, profile_high_weight, "high_word_weight")
.await
.expect("Failed to analyze");
println!("Low word_count weight (20%) score: {}", report_low.score);
println!("High word_count weight (80%) score: {}", report_high.score);
assert!(report_low.score > 0.0);
assert!(report_high.score > 0.0);
}
#[tokio::test]
async fn test_combined_category_and_metric_weights() {
let mut weights1 = HashMap::new();
weights1.insert("content".to_string(), 0.50);
weights1.insert("seo".to_string(), 0.50);
let profile_1 = ProfileBuilder::new("profile_1")
.with_category_weights(weights1)
.with_metric_weight("word_count", 0.50)
.unwrap()
.build()
.expect("Failed to build profile");
let mut weights2 = HashMap::new();
weights2.insert("content".to_string(), 1.00);
let profile_2 = ProfileBuilder::new("profile_2")
.with_category_weights(weights2)
.with_metric_weight("word_count", 0.25)
.unwrap()
.build()
.expect("Failed to build profile");
let report_1 = analyze_with_custom_profile(TEST_HTML, profile_1, "profile_1")
.await
.expect("Failed to analyze");
let report_2 = analyze_with_custom_profile(TEST_HTML, profile_2, "profile_2")
.await
.expect("Failed to analyze");
println!("Profile 1 (50% × 50% = 25%) score: {}", report_1.score);
println!("Profile 2 (100% × 25% = 25%) score: {}", report_2.score);
}
#[tokio::test]
async fn test_all_categories_equal_weight() {
let num_categories = 5;
let equal_weight = 1.0 / num_categories as f32;
let mut weights = HashMap::new();
weights.insert("content".to_string(), equal_weight);
weights.insert("seo".to_string(), equal_weight);
weights.insert("structure".to_string(), equal_weight);
weights.insert("media".to_string(), equal_weight);
weights.insert("accessibility".to_string(), equal_weight);
let profile = ProfileBuilder::new("all_equal")
.with_category_weights(weights)
.build()
.expect("Failed to build profile");
let total: f32 = profile.category_weights.values().sum();
assert!(
(total - 1.0).abs() < 0.001,
"Equal weights should sum to 1.0, got {}",
total
);
let report = analyze_with_custom_profile(TEST_HTML, profile, "all_equal")
.await
.expect("Failed to analyze");
println!(
"All categories equal weight (20% each) score: {}",
report.score
);
}
#[tokio::test]
async fn test_metric_weight_zero_vs_disabled() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
let profile_zero_weight = ProfileBuilder::new("zero_weight")
.with_category_weights(weights.clone())
.with_metric_weight("word_count", 0.0)
.unwrap()
.build()
.expect("Failed to build profile");
let profile_disabled = ProfileBuilder::new("disabled")
.with_category_weights(weights)
.with_metric_enabled("word_count", false)
.build()
.expect("Failed to build profile");
let report_zero = analyze_with_custom_profile(TEST_HTML, profile_zero_weight, "zero_weight")
.await
.expect("Failed to analyze");
let report_disabled = analyze_with_custom_profile(TEST_HTML, profile_disabled, "disabled")
.await
.expect("Failed to analyze");
println!("Zero weight score: {}", report_zero.score);
println!("Disabled metric score: {}", report_disabled.score);
}
#[test]
fn test_weight_arithmetic() {
let content_weight = 0.60_f32;
let seo_weight = 0.40_f32;
let total = content_weight + seo_weight;
assert!((total - 1.0).abs() < 0.001, "Weights should sum to 1.0");
let metric_weight_in_category = 0.50_f32;
let final_metric_weight = content_weight * metric_weight_in_category;
assert_eq!(final_metric_weight, 0.30); }