use std::fs;
use tempfile::tempdir;
use webpage_quality_analyzer::async_runtime::DefaultRuntime;
use webpage_quality_analyzer::*;
fn create_minimal_yaml_config() -> String {
r#"
active_profile: "test_profile"
presets:
test_profile:
metadata:
name: "Test Profile"
description: "Minimal test configuration"
target_content_types: ["article"]
version: "1.0.0"
author: "test"
created_at: "2025-01-01T00:00:00Z"
tags: ["test"]
category_weights:
content: 0.4
seo: 0.3
technical: 0.2
accessibility: 0.1
structure: 0.0
media: 0.0
links: 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: 100
min_paragraph_count: 3
min_heading_count: 2
expected_heading_structure: ["h1", "h2"]
min_main_text_ratio: 0.5
min_image_alt_coverage: 0.8
required_meta_tags: ["description"]
recommended_meta_tags: ["keywords", "author"]
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:
include_debug: false
include_raw_metrics: true
timestamp_format: "ISO8601"
float_precision: 2
"#
.to_string()
}
fn create_json_config_with_overrides() -> String {
r#"{
"active_profile": "custom_profile",
"presets": {
"custom_profile": {
"metadata": {
"name": "Custom Profile",
"description": "Profile with custom metric weights",
"target_content_types": ["blog", "article"],
"version": "1.0.0",
"author": "test",
"created_at": "2025-01-01T00:00:00Z",
"tags": ["custom", "test"]
},
"category_weights": {
"content": 0.5,
"seo": 0.2,
"technical": 0.15,
"accessibility": 0.15,
"structure": 0.0,
"media": 0.0,
"links": 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": {
"word_count": {
"weight": 2.0,
"enabled": true,
"thresholds": null,
"penalty_multiplier": 1.0,
"bonus_conditions": [],
"scoring_function_override": null
},
"readability_fk": {
"weight": 1.5,
"enabled": true,
"thresholds": null,
"penalty_multiplier": 1.0,
"bonus_conditions": [],
"scoring_function_override": null
}
},
"content_expectations": {
"min_word_count": 500,
"min_paragraph_count": 5,
"min_heading_count": 3,
"expected_heading_structure": ["h1", "h2", "h3"],
"min_main_text_ratio": 0.6,
"min_image_alt_coverage": 0.9,
"required_meta_tags": ["description", "keywords"],
"recommended_meta_tags": ["author", "publish_date"]
},
"quality_bands": {
"excellent": 90.0,
"good": 70.0,
"fair": 50.0,
"poor": 30.0
},
"penalties": {
"severe_penalties": {},
"moderate_penalties": {},
"light_penalties": {}
},
"bonuses": {
"excellence_bonuses": {},
"achievement_bonuses": {},
"synergy_bonuses": {}
}
}
},
"output": {
"include_debug": true,
"include_raw_metrics": true,
"timestamp_format": "ISO8601",
"float_precision": 3
}
}"#
.to_string()
}
fn create_toml_config() -> String {
r#"
active_profile = "toml_profile"
[output]
include_debug = false
include_raw_metrics = false
timestamp_format = "ISO8601"
float_precision = 2
[presets.toml_profile.metadata]
name = "TOML Profile"
description = "Testing TOML configuration format"
target_content_types = ["article"]
version = "1.0.0"
author = "test"
created_at = "2025-01-01T00:00:00Z"
tags = ["toml", "test"]
[presets.toml_profile.category_weights]
content = 0.6
seo = 0.2
technical = 0.1
accessibility = 0.1
structure = 0.0
media = 0.0
links = 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
[presets.toml_profile.metric_overrides]
[presets.toml_profile.content_expectations]
min_word_count = 200
min_paragraph_count = 4
min_heading_count = 2
expected_heading_structure = ["h1", "h2"]
min_main_text_ratio = 0.5
min_image_alt_coverage = 0.75
required_meta_tags = ["description"]
recommended_meta_tags = ["keywords"]
[presets.toml_profile.quality_bands]
excellent = 85.0
good = 65.0
fair = 45.0
poor = 25.0
[presets.toml_profile.penalties]
severe_penalties = {}
moderate_penalties = {}
light_penalties = {}
[presets.toml_profile.bonuses]
excellence_bonuses = {}
achievement_bonuses = {}
synergy_bonuses = {}
"#
.to_string()
}
fn get_test_html() -> &'static str {
r#"
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test Article - Quality Analysis</title>
<meta name="description" content="This is a comprehensive test article for configuration testing">
<meta name="keywords" content="test, quality, analysis">
<meta name="author" content="Test Author">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<header>
<h1>Test Article Title</h1>
</header>
<main>
<article>
<h2>Introduction</h2>
<p>This is the first paragraph with substantial content. It contains enough words to be considered meaningful for quality analysis purposes. We want to ensure that all configuration options are properly tested.</p>
<p>Here's a second paragraph providing more context and content depth. The analyzer should process this content according to the active configuration profile settings.</p>
<h2>Main Content Section</h2>
<p>This section contains the primary content of our test article. It includes multiple paragraphs, proper heading structure, and various HTML elements to trigger different metrics.</p>
<p>Another paragraph with more detailed information. Quality analyzers need substantial content to provide accurate scoring and recommendations.</p>
<p>A third paragraph ensures we meet minimum content requirements. Configuration profiles can specify different thresholds for word count, paragraph count, and other content metrics.</p>
<h3>Subsection with Details</h3>
<p>Nested heading structures are important for SEO and content organization. This subsection provides additional detail and demonstrates proper HTML hierarchy.</p>
<p>More content here to ensure we have enough text for various analysis metrics including readability scores and content density calculations.</p>
<h2>Conclusion</h2>
<p>The final section wraps up the content with concluding remarks. This ensures we have a complete article structure with beginning, middle, and end.</p>
<p>Last paragraph providing closure and maintaining content quality throughout the entire document.</p>
</article>
</main>
<footer>
<p>© 2025 Test Organization</p>
</footer>
</body>
</html>
"#
}
#[tokio::test]
async fn test_yaml_config_file_loading() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("test.yaml");
fs::write(&config_path, create_minimal_yaml_config()).unwrap();
let result = from_config_file(&config_path);
assert!(
result.is_ok(),
"Failed to load YAML config: {:?}",
result.err()
);
let analyzer = result.unwrap();
let profile_name = analyzer.get_active_profile_name().unwrap();
assert_eq!(profile_name, "test_profile");
}
#[tokio::test]
async fn test_json_config_file_loading() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("test.json");
fs::write(&config_path, create_json_config_with_overrides()).unwrap();
let result = from_config_file(&config_path);
assert!(
result.is_ok(),
"Failed to load JSON config: {:?}",
result.err()
);
let analyzer = result.unwrap();
let profile_name = analyzer.get_active_profile_name().unwrap();
assert_eq!(profile_name, "custom_profile");
}
#[tokio::test]
async fn test_toml_config_file_loading() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("test.toml");
fs::write(&config_path, create_toml_config()).unwrap();
let result = from_config_file(&config_path);
assert!(
result.is_ok(),
"Failed to load TOML config: {:?}",
result.err()
);
let analyzer = result.unwrap();
let profile_name = analyzer.get_active_profile_name().unwrap();
assert_eq!(profile_name, "toml_profile");
}
#[tokio::test]
async fn test_config_file_analysis_execution() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("analysis_test.yaml");
fs::write(&config_path, create_minimal_yaml_config()).unwrap();
let analyzer = from_config_file(&config_path).unwrap();
let html = get_test_html();
let result = analyzer.run("https://example.com/test", Some(html)).await;
assert!(result.is_ok(), "Analysis failed: {:?}", result.err());
let report = result.unwrap();
assert!(report.score >= 0.0 && report.score <= 100.0);
assert!(report.metrics.html_analysis.content.word_count > 0);
}
#[tokio::test]
async fn test_config_file_with_multiple_profiles() {
let config = r#"
active_profile: "profile_a"
presets:
profile_a:
metadata:
name: "Profile A"
description: "First profile"
target_content_types: ["article"]
version: "1.0.0"
author: "test"
created_at: "2025-01-01T00:00:00Z"
tags: ["test"]
category_weights:
content: 0.5
seo: 0.3
technical: 0.2
accessibility: 0.0
structure: 0.0
media: 0.0
links: 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: 100
min_paragraph_count: 2
min_heading_count: 1
expected_heading_structure: []
min_main_text_ratio: 0.5
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: {}
profile_b:
metadata:
name: "Profile B"
description: "Second profile"
target_content_types: ["blog"]
version: "1.0.0"
author: "test"
created_at: "2025-01-01T00:00:00Z"
tags: ["test"]
category_weights:
content: 0.4
seo: 0.4
technical: 0.2
accessibility: 0.0
structure: 0.0
media: 0.0
links: 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: 200
min_paragraph_count: 3
min_heading_count: 2
expected_heading_structure: []
min_main_text_ratio: 0.6
min_image_alt_coverage: 0.8
required_meta_tags: []
recommended_meta_tags: []
quality_bands:
excellent: 90.0
good: 70.0
fair: 50.0
poor: 30.0
penalties:
severe_penalties: {}
moderate_penalties: {}
light_penalties: {}
bonuses:
excellence_bonuses: {}
achievement_bonuses: {}
synergy_bonuses: {}
"#;
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("multi_profile.yaml");
fs::write(&config_path, config).unwrap();
let analyzer = from_config_file(&config_path).unwrap();
let active_profile = analyzer.get_active_profile_name().unwrap();
assert_eq!(active_profile, "profile_a");
}
#[tokio::test]
async fn test_builder_with_default_profile() {
let result = Analyzer::<DefaultRuntime>::builder().with_profile_name("content_article");
assert!(result.is_ok(), "Failed to set profile: {:?}", result.err());
let builder_result = result.unwrap().build();
assert!(
builder_result.is_ok(),
"Failed to build analyzer: {:?}",
builder_result.err()
);
}
#[tokio::test]
async fn test_builder_with_all_built_in_profiles() {
let profiles = vec!["content_article", "news", "blog", "product", "general"];
for profile_name in profiles {
let result = Analyzer::<DefaultRuntime>::builder()
.with_profile_name(profile_name)
.and_then(|b| b.build());
assert!(
result.is_ok(),
"Failed to build analyzer with '{}' profile: {:?}",
profile_name,
result.err()
);
}
}
#[tokio::test]
async fn test_builder_with_linkcheck_enabled() {
let analyzer = Analyzer::<DefaultRuntime>::builder()
.with_profile_name("content_article")
.unwrap()
.enable_linkcheck(true)
.build()
.unwrap();
let html = get_test_html();
let result = analyzer.run("https://example.com", Some(html)).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_builder_with_nlp_enabled() {
let analyzer = Analyzer::<DefaultRuntime>::builder()
.with_profile_name("content_article")
.unwrap()
.enable_nlp(true)
.build()
.unwrap();
let html = get_test_html();
let result = analyzer.run("https://example.com", Some(html)).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_builder_with_custom_linkcheck_sample() {
let analyzer = Analyzer::<DefaultRuntime>::builder()
.with_profile_name("content_article")
.unwrap()
.enable_linkcheck(true)
.linkcheck_sample(10)
.build()
.unwrap();
let html = get_test_html();
let result = analyzer.run("https://example.com", Some(html)).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_builder_with_config_path() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("builder_test.yaml");
fs::write(&config_path, create_minimal_yaml_config()).unwrap();
let result = Analyzer::<DefaultRuntime>::builder().with_config_path(&config_path);
assert!(
result.is_ok(),
"Failed to load config path: {:?}",
result.err()
);
let analyzer = result.unwrap().build().unwrap();
let html = get_test_html();
let report = analyzer
.run("https://example.com", Some(html))
.await
.unwrap();
assert!(report.score >= 0.0);
}
#[tokio::test]
async fn test_builder_chaining_multiple_options() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("chain_test.yaml");
fs::write(&config_path, create_minimal_yaml_config()).unwrap();
let analyzer = Analyzer::<DefaultRuntime>::builder()
.with_config_path(&config_path)
.unwrap()
.enable_nlp(true)
.enable_linkcheck(false)
.add_report(true)
.build()
.unwrap();
let html = get_test_html();
let result = analyzer.run("https://example.com", Some(html)).await;
assert!(result.is_ok());
}
#[test]
fn test_config_manager_creation() {
let manager = ConfigManager::new();
let active_profile = manager.get_active_profile_name();
assert!(active_profile.is_ok());
}
#[test]
fn test_config_manager_get_profile() {
let manager = ConfigManager::new();
let profile = manager.get_profile("content_article");
assert!(
profile.is_some(),
"Default profile 'content_article' should exist"
);
}
#[test]
fn test_config_manager_from_yaml_file() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("manager_test.yaml");
fs::write(&config_path, create_minimal_yaml_config()).unwrap();
let result = ConfigManager::from_file(&config_path);
assert!(
result.is_ok(),
"Failed to load ConfigManager from YAML: {:?}",
result.err()
);
let manager = result.unwrap();
let profile = manager.get_profile("test_profile");
assert!(profile.is_some());
}
#[test]
fn test_config_manager_from_json_file() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("manager_test.json");
fs::write(&config_path, create_json_config_with_overrides()).unwrap();
let result = ConfigManager::from_file(&config_path);
assert!(
result.is_ok(),
"Failed to load ConfigManager from JSON: {:?}",
result.err()
);
}
#[test]
fn test_config_manager_from_toml_file() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("manager_test.toml");
fs::write(&config_path, create_toml_config()).unwrap();
let result = ConfigManager::from_file(&config_path);
assert!(
result.is_ok(),
"Failed to load ConfigManager from TOML: {:?}",
result.err()
);
}
#[test]
fn test_config_manager_profile_switching() {
let config = r#"
active_profile: "profile_one"
presets:
profile_one:
metadata:
name: "Profile One"
description: "First profile"
target_content_types: ["article"]
version: "1.0.0"
author: "test"
created_at: "2025-01-01T00:00:00Z"
tags: ["test"]
category_weights:
content: 1.0
seo: 0.0
technical: 0.0
accessibility: 0.0
structure: 0.0
media: 0.0
links: 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: 1
min_heading_count: 1
expected_heading_structure: []
min_main_text_ratio: 0.5
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: {}
profile_two:
metadata:
name: "Profile Two"
description: "Second profile"
target_content_types: ["blog"]
version: "1.0.0"
author: "test"
created_at: "2025-01-01T00:00:00Z"
tags: ["test"]
category_weights:
content: 0.5
seo: 0.5
technical: 0.0
accessibility: 0.0
structure: 0.0
media: 0.0
links: 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: 100
min_paragraph_count: 2
min_heading_count: 1
expected_heading_structure: []
min_main_text_ratio: 0.5
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: {}
"#;
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("switch_test.yaml");
fs::write(&config_path, config).unwrap();
let mut manager = ConfigManager::from_file(&config_path).unwrap();
assert_eq!(manager.get_active_profile_name().unwrap(), "profile_one");
let result = manager.set_active_profile("profile_two");
assert!(
result.is_ok(),
"Failed to switch profile: {:?}",
result.err()
);
assert_eq!(manager.get_active_profile_name().unwrap(), "profile_two");
}
#[test]
fn test_nonexistent_config_file() {
let result = from_config_file("nonexistent_file.yaml");
assert!(result.is_err(), "Should fail with nonexistent file");
}
#[test]
fn test_invalid_yaml_config() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("invalid.yaml");
fs::write(&config_path, "invalid: yaml: content:").unwrap();
let result = from_config_file(&config_path);
assert!(result.is_err(), "Should fail with invalid YAML");
}
#[test]
fn test_invalid_json_config() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("invalid.json");
fs::write(&config_path, r#"{"invalid": "json"#).unwrap();
let result = from_config_file(&config_path);
assert!(result.is_err(), "Should fail with invalid JSON");
}
#[test]
fn test_unsupported_file_extension() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("config.txt");
fs::write(&config_path, "some content").unwrap();
let result = from_config_file(&config_path);
assert!(result.is_err(), "Should fail with unsupported extension");
}
#[tokio::test]
async fn test_builder_with_invalid_profile_name() {
let result = Analyzer::<DefaultRuntime>::builder()
.with_profile_name("nonexistent_profile")
.and_then(|b| b.build());
assert!(result.is_err(), "Should fail with invalid profile name");
}
#[test]
fn test_config_with_missing_required_fields() {
let incomplete_config = r#"
active_profile: "incomplete"
presets:
incomplete:
metadata:
name: "Incomplete"
"#;
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("incomplete.yaml");
fs::write(&config_path, incomplete_config).unwrap();
let result = ConfigManager::from_file(&config_path);
assert!(result.is_err(), "Should fail with missing required fields");
}
#[tokio::test]
async fn test_all_builtin_profiles_work() {
let profiles = vec!["content_article", "news", "blog", "product", "general"];
let html = get_test_html();
for profile in profiles {
let result = analyze_with_profile("https://example.com", Some(html), profile).await;
assert!(
result.is_ok(),
"Profile '{}' should work: {:?}",
profile,
result.err()
);
let report = result.unwrap();
assert!(report.score >= 0.0 && report.score <= 100.0);
}
}
#[tokio::test]
async fn test_profile_affects_scoring() {
let html = r#"
<html>
<head>
<title>SEO Optimized Page</title>
<meta name="description" content="Great description">
<meta name="keywords" content="seo, test">
</head>
<body>
<h1>Title</h1>
<p>Some content here.</p>
</body>
</html>
"#;
let report_article = analyze_with_profile("https://example.com", Some(html), "content_article")
.await
.unwrap();
let report_product = analyze_with_profile("https://example.com", Some(html), "product")
.await
.unwrap();
assert!(report_article.score >= 0.0 && report_article.score <= 100.0);
assert!(report_product.score >= 0.0 && report_product.score <= 100.0);
println!("Article score: {}", report_article.score);
println!("Product score: {}", report_product.score);
}
#[tokio::test]
async fn test_output_configuration_options() {
let config = r#"
active_profile: "test_output"
presets:
test_output:
metadata:
name: "Output Test"
description: "Testing output configuration"
target_content_types: ["article"]
version: "1.0.0"
author: "test"
created_at: "2025-01-01T00:00:00Z"
tags: ["test"]
category_weights:
content: 1.0
seo: 0.0
technical: 0.0
accessibility: 0.0
structure: 0.0
media: 0.0
links: 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: 1
min_heading_count: 1
expected_heading_structure: []
min_main_text_ratio: 0.5
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:
include_debug: true
include_raw_metrics: true
timestamp_format: "ISO8601"
float_precision: 3
"#;
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("output_test.yaml");
fs::write(&config_path, config).unwrap();
let analyzer = from_config_file(&config_path).unwrap();
let html = get_test_html();
let report = analyzer
.run("https://example.com", Some(html))
.await
.unwrap();
assert!(report.score >= 0.0);
assert!(report.metrics.html_analysis.content.word_count > 0);
}
#[tokio::test]
async fn test_full_workflow_with_custom_config() {
let config = create_json_config_with_overrides();
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("workflow.json");
fs::write(&config_path, config).unwrap();
let analyzer = from_config_file(&config_path).unwrap();
let html = get_test_html();
let report = analyzer
.run("https://example.com/article", Some(html))
.await
.unwrap();
assert!(report.score >= 0.0 && report.score <= 100.0);
assert_eq!(report.url, "https://example.com/article");
assert!(report.metrics.html_analysis.content.word_count > 0);
assert!(report.fetched_at.is_some());
}
#[tokio::test]
async fn test_compare_level1_and_level3_apis() {
let html = get_test_html();
let report_level1 = analyze("https://example.com", Some(html)).await.unwrap();
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("compare.yaml");
fs::write(&config_path, create_minimal_yaml_config()).unwrap();
let analyzer = from_config_file(&config_path).unwrap();
let report_level3 = analyzer
.run("https://example.com", Some(html))
.await
.unwrap();
assert!(report_level1.score >= 0.0);
assert!(report_level3.score >= 0.0);
assert_eq!(
report_level1.metrics.html_analysis.content.word_count,
report_level3.metrics.html_analysis.content.word_count
);
}
#[tokio::test]
async fn test_config_persistence_across_multiple_analyses() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("persist.yaml");
fs::write(&config_path, create_minimal_yaml_config()).unwrap();
let analyzer = from_config_file(&config_path).unwrap();
let html1 = get_test_html();
let html2 = "<html><head><title>Different</title></head><body><p>Content</p></body></html>";
let report1 = analyzer
.run("https://example.com/page1", Some(html1))
.await
.unwrap();
let report2 = analyzer
.run("https://example.com/page2", Some(html2))
.await
.unwrap();
assert!(report1.score >= 0.0);
assert!(report2.score >= 0.0);
assert_ne!(
report1.metrics.html_analysis.content.word_count,
report2.metrics.html_analysis.content.word_count
);
}
#[test]
fn test_config_format_auto_detection() {
let temp_dir = tempdir().unwrap();
let yaml_path = temp_dir.path().join("test.yaml");
fs::write(&yaml_path, create_minimal_yaml_config()).unwrap();
assert!(ConfigManager::from_file(&yaml_path).is_ok());
let yml_path = temp_dir.path().join("test.yml");
fs::write(&yml_path, create_minimal_yaml_config()).unwrap();
assert!(ConfigManager::from_file(&yml_path).is_ok());
let json_path = temp_dir.path().join("test.json");
fs::write(&json_path, create_json_config_with_overrides()).unwrap();
assert!(ConfigManager::from_file(&json_path).is_ok());
let toml_path = temp_dir.path().join("test.toml");
fs::write(&toml_path, create_toml_config()).unwrap();
assert!(ConfigManager::from_file(&toml_path).is_ok());
}
#[tokio::test]
async fn test_config_loading_performance() {
use std::time::Instant;
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("perf.yaml");
fs::write(&config_path, create_minimal_yaml_config()).unwrap();
let start = Instant::now();
let _ = from_config_file(&config_path).unwrap();
let duration = start.elapsed();
assert!(
duration.as_millis() < 100,
"Config loading took {:?}",
duration
);
}
#[tokio::test]
async fn test_analysis_with_config_performance() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("analysis_perf.yaml");
fs::write(&config_path, create_minimal_yaml_config()).unwrap();
let analyzer = from_config_file(&config_path).unwrap();
let html = get_test_html();
use std::time::Instant;
let start = Instant::now();
let _ = analyzer
.run("https://example.com", Some(html))
.await
.unwrap();
let duration = start.elapsed();
assert!(duration.as_secs() < 2, "Analysis took {:?}", duration);
}