webpage_quality_analyzer 1.0.2

High-performance webpage quality analyzer with 115 comprehensive metrics - Rust library with WASM, C++, and Python bindings
Documentation
use std::fs;
/// Integration tests for all three API levels
/// Tests the simple, intermediate, and advanced API usage patterns
use webpage_quality_analyzer::test_fixtures::*;

/// Test Level 1 API - Simple analyze function
#[tokio::test]
async fn test_level1_api_simple_analyze() {
    let result = webpage_quality_analyzer::analyze(
        "https://example.com/test-page",
        Some(COMPREHENSIVE_HTML),
    )
    .await;

    assert!(
        result.is_ok(),
        "Level 1 API should work with comprehensive HTML"
    );

    let report = result.unwrap();

    // Basic validations
    assert!(
        report.score >= 0.0 && report.score <= 100.0,
        "Score should be between 0-100"
    );
    assert_eq!(report.url, "https://example.com/test-page");
    assert_eq!(report.metadata.title, "Test Page");
    assert!(
        report.metadata.meta_description.is_some(),
        "Should extract meta description"
    );
}

/// Test Level 2 API - Profile-based analysis
#[tokio::test]
async fn test_level2_api_with_profiles() {
    // Test all profiles including news, product, portfolio after fixes
    let profiles = ["content_article", "news", "product", "portfolio"];

    for profile in &profiles {
        let result = webpage_quality_analyzer::analyze_with_profile(
            "https://example.com/test-page",
            Some(COMPREHENSIVE_HTML),
            profile,
        )
        .await;

        assert!(
            result.is_ok(),
            "Level 2 API should work with profile: {} - Error: {:?}",
            profile,
            result.as_ref().err()
        );

        let report = result.unwrap();
        assert!(report.score >= 0.0 && report.score <= 100.0);

        // Each profile might score differently
        println!(
            "Profile {}: Score = {:.1}, Verdict = {}",
            profile, report.score, report.verdict
        );
    }
}

/// Test Level 2 API with invalid profile
#[tokio::test]
async fn test_level2_api_invalid_profile() {
    let result = webpage_quality_analyzer::analyze_with_profile(
        "https://example.com/test-page",
        Some(COMPREHENSIVE_HTML),
        "nonexistent_profile",
    )
    .await;

    assert!(result.is_err(), "Should fail with invalid profile");
}

/// Test Level 3 API with config file
#[tokio::test]
async fn test_level3_api_config_file() {
    // Use the exact working config from config_tests.rs with profile name changed
    let config_content = r#"
active_profile: "test_profile"

presets:
  test_profile:
    metadata:
      name: "Test Profile"
      description: "Test configuration profile"
      target_content_types: ["article", "blog"]
      version: "1.0.0"
      author: "test"
      created_at: "2025-01-01T00:00:00Z"
      tags: ["test"]
    category_weights:
      content: 0.5
      structure: 0.3
      seo: 0.2
      technical: 0.0
      media: 0.0
      links: 0.0
      accessibility: 0.0
      mobile: 0.0
      authority: 0.0
      language: 0.0
      forms: 0.0
      structureddata: 0.0
      branding: 0.0
      userexperience: 0.0
      business: 0.0
      internationalization: 0.0
      performance: 0.0
      security: 0.0
      analytics: 0.0
      errorhandling: 0.0
    metric_overrides: {}
    content_expectations:
      min_word_count: 50
      min_paragraph_count: 2
      min_heading_count: 1
      expected_heading_structure: []
      min_main_text_ratio: 0.1
      min_image_alt_coverage: 0.7
      required_meta_tags: []
      recommended_meta_tags: []
    quality_bands:
      excellent: 85.0
      good: 65.0
      fair: 45.0
      poor: 25.0
    penalties:
      severe_penalties: {}
      moderate_penalties: {}
      light_penalties: {}
    bonuses:
      excellence_bonuses: {}
      achievement_bonuses: {}
      synergy_bonuses: {}

output:
  verbosity: "normal"
  include_metadata: true
  include_processed_document: false
  include_debug: false
  include_raw_metrics: false
  timestamp_format: "ISO8601"
  float_precision: 2
"#;

    let config_path = "test_config.yaml";
    fs::write(config_path, config_content).expect("Should write config file");

    // Test Level 3 API
    let result = webpage_quality_analyzer::from_config_file(config_path);
    if let Err(ref e) = result {
        println!("Config file loading error: {:?}", e);
    }
    assert!(result.is_ok(), "Level 3 API should load config file");

    let analyzer = result.unwrap();
    let analysis_result = analyzer
        .run("https://example.com/test-page", Some(COMPREHENSIVE_HTML))
        .await;

    if let Err(ref e) = analysis_result {
        println!("Analysis error: {:?}", e);
    }
    assert!(
        analysis_result.is_ok(),
        "Analysis should succeed with custom config"
    );

    let report = analysis_result.unwrap();
    assert!(report.score >= 0.0 && report.score <= 100.0);
    // assert_eq!(report.profile_name, "test_profile"); // profile_name field doesn't exist

    // Cleanup
    let _ = fs::remove_file(config_path);
}

/// Test Level 3 API with nonexistent config file
#[test]
fn test_level3_api_nonexistent_config() {
    let result = webpage_quality_analyzer::from_config_file("nonexistent_config.yaml");
    assert!(result.is_err(), "Should fail with nonexistent config file");
}

/// Test Builder pattern API - DISABLED (old API)
/*
#[test]
fn test_builder_api_comprehensive() {
    let result = Analyzer::builder()
        .with_profile_name("content_article")
        .unwrap()
        .enable_linkcheck(true)
        .linkcheck_sample(25)
        .enable_nlp(false)
        .add_report(true)
        .build();

    assert!(result.is_ok(), "Builder should create analyzer successfully");

    let analyzer = result.unwrap();
    let analysis_result = analyzer.run("https://example.com/test-page", Some(COMPREHENSIVE_HTML)).await;

    assert!(analysis_result.is_ok(), "Analysis should succeed with builder-configured analyzer");

    let report = analysis_result.unwrap();
    assert!(report.score >= 0.0 && report.score <= 100.0);

    // Verify some expected metrics from comprehensive HTML
    assert!(report.metadata.title.contains("Comprehensive"));
    assert!(report.metadata.meta_description.is_some());
    assert!(report.metadata.canonical_url.is_some());
    assert!(report.metadata.og_title.is_some());
}
*/

/// Test error handling with malformed HTML
#[tokio::test]
async fn test_error_handling_malformed_html() {
    let malformed_html = MALFORMED_HTML;

    let result =
        webpage_quality_analyzer::analyze("https://example.com/malformed", Some(malformed_html))
            .await;

    // Should still succeed - parser is resilient
    assert!(result.is_ok(), "Should handle malformed HTML gracefully");

    let report = result.unwrap();
    assert!(report.score >= 0.0 && report.score <= 100.0);
}

/// Test with minimal HTML
#[tokio::test]
async fn test_minimal_html() {
    let minimal_html = MINIMAL_HTML;

    let result =
        webpage_quality_analyzer::analyze("https://example.com/minimal", Some(minimal_html)).await;

    assert!(result.is_ok(), "Should handle minimal HTML");

    let report = result.unwrap();
    assert!(report.score >= 0.0 && report.score <= 100.0);
    // Minimal HTML should typically score lower
    assert!(report.score < 80.0, "Minimal HTML should have lower score");
}

/// Test with empty HTML
#[tokio::test]
async fn test_empty_html() {
    let result = webpage_quality_analyzer::analyze("https://example.com/empty", Some("")).await;

    assert!(result.is_ok(), "Should handle empty HTML");

    let report = result.unwrap();
    assert!(report.score >= 0.0 && report.score <= 100.0);
    // Empty HTML should score very low
    assert!(report.score < 50.0, "Empty HTML should have very low score");
}

/// Test profile comparison with same content
#[tokio::test]
async fn test_profile_comparison() {
    // Test all profiles including news, product, portfolio after fixes
    let profiles = ["content_article", "news", "product", "portfolio"];
    let mut results = Vec::new();

    for profile in &profiles {
        let result = webpage_quality_analyzer::analyze_with_profile(
            "https://example.com/comparison",
            Some(COMPREHENSIVE_HTML),
            profile,
        )
        .await;

        assert!(
            result.is_ok(),
            "Profile {} should work - Error: {:?}",
            profile,
            result.as_ref().err()
        );
        results.push((profile, result.unwrap()));
    }

    // All profiles should produce valid scores
    for (profile, report) in &results {
        assert!(report.score >= 0.0 && report.score <= 100.0);
        // assert_eq!(report.profile_name, **profile); // profile_name field doesn't exist
        println!("Profile {}: Score = {:.1}", profile, report.score);
    }

    // Scores may vary between profiles due to different weightings
    let scores: Vec<f32> = results.iter().map(|(_, report)| report.score).collect();
    assert!(scores.len() == 4, "Should have 4 profile results");
}