"""
SEO诊断服务单元测试
"""
import pytest
from app.services.seo_diagnosis import (
    SEODiagnosisService,
    SEODiagnosisResult,
    SEODimensionScore,
    DiagnosisItem,
    SEORecommendation,
    DiagnosisStatus,
    DimensionName,
    TechnicalSEOData,
    OnPageSEOData,
    ContentQualityData,
    BacklinkData,
    UserExperienceData,
    diagnose_technical_seo,
    diagnose_on_page_seo,
    diagnose_content_quality,
    diagnose_backlinks,
    diagnose_user_experience,
    generate_recommendations,
)


class TestDiagnosisStatus:
    """诊断状态枚举测试"""

    def test_status_values(self):
        """测试状态值"""
        assert DiagnosisStatus.PASS == "pass"
        assert DiagnosisStatus.WARNING == "warning"
        assert DiagnosisStatus.FAIL == "fail"


class TestDimensionName:
    """维度名称枚举测试"""

    def test_dimension_names(self):
        """测试维度名称"""
        assert DimensionName.TECHNICAL_SEO == "技术SEO"
        assert DimensionName.ON_PAGE_SEO == "页面SEO"
        assert DimensionName.CONTENT_QUALITY == "内容质量"
        assert DimensionName.BACKLINK_ANALYSIS == "外链分析"
        assert DimensionName.USER_EXPERIENCE == "用户体验"


class TestDiagnosisItem:
    """诊断项数据结构测试"""

    def test_create_item(self):
        """测试创建诊断项"""
        item = DiagnosisItem(
            name="测试项",
            status=DiagnosisStatus.PASS,
            description="测试描述",
            suggestion="测试建议",
            score=1.0,
        )
        assert item.name == "测试项"
        assert item.status == DiagnosisStatus.PASS
        assert item.score == 1.0

    def test_item_with_details(self):
        """测试带详情的诊断项"""
        item = DiagnosisItem(
            name="测试项",
            status=DiagnosisStatus.WARNING,
            description="测试描述",
            suggestion="测试建议",
            details={"key": "value"},
        )
        assert item.details == {"key": "value"}


class TestSEODimensionScore:
    """维度评分数据结构测试"""

    def test_create_dimension_score(self):
        """测试创建维度评分"""
        dim = SEODimensionScore(
            name="测试维度",
            score=20.0,
            max_score=25.0,
            items=[],
            status=DiagnosisStatus.PASS,
        )
        assert dim.score == 20.0
        assert dim.max_score == 25.0
        assert dim.percentage == 80.0

    def test_percentage_calculation(self):
        """测试得分率计算"""
        dim = SEODimensionScore(
            name="测试维度",
            score=15.0,
            max_score=25.0,
            items=[],
            status=DiagnosisStatus.PASS,
        )
        assert dim.percentage == 60.0

    def test_status_calculation_all_pass(self):
        """测试全部通过时的状态"""
        items = [
            DiagnosisItem(name="项1", status=DiagnosisStatus.PASS, description="", suggestion=""),
            DiagnosisItem(name="项2", status=DiagnosisStatus.PASS, description="", suggestion=""),
        ]
        dim = SEODimensionScore(
            name="测试维度",
            score=10.0,
            max_score=10.0,
            items=items,
            status=DiagnosisStatus.PASS,
        )
        assert dim.status == DiagnosisStatus.PASS

    def test_status_calculation_with_warnings(self):
        """测试有警告时的状态"""
        items = [
            DiagnosisItem(name="项1", status=DiagnosisStatus.PASS, description="", suggestion=""),
            DiagnosisItem(name="项2", status=DiagnosisStatus.WARNING, description="", suggestion=""),
            DiagnosisItem(name="项3", status=DiagnosisStatus.WARNING, description="", suggestion=""),
        ]
        dim = SEODimensionScore(
            name="测试维度",
            score=7.0,
            max_score=10.0,
            items=items,
            status=DiagnosisStatus.PASS,
        )
        assert dim.status == DiagnosisStatus.WARNING

    def test_status_calculation_with_fails(self):
        """测试有失败时的状态"""
        items = [
            DiagnosisItem(name="项1", status=DiagnosisStatus.FAIL, description="", suggestion=""),
            DiagnosisItem(name="项2", status=DiagnosisStatus.PASS, description="", suggestion=""),
            DiagnosisItem(name="项3", status=DiagnosisStatus.PASS, description="", suggestion=""),
            DiagnosisItem(name="项4", status=DiagnosisStatus.PASS, description="", suggestion=""),
        ]
        dim = SEODimensionScore(
            name="测试维度",
            score=7.0,
            max_score=10.0,
            items=items,
            status=DiagnosisStatus.PASS,
        )
        # 1个FAIL在4个项中占25%，未超过30%，但有FAIL所以是WARNING
        assert dim.status == DiagnosisStatus.WARNING

    def test_status_calculation_many_fails(self):
        """测试大量失败时的状态"""
        items = [
            DiagnosisItem(name="项1", status=DiagnosisStatus.FAIL, description="", suggestion=""),
            DiagnosisItem(name="项2", status=DiagnosisStatus.FAIL, description="", suggestion=""),
            DiagnosisItem(name="项3", status=DiagnosisStatus.FAIL, description="", suggestion=""),
            DiagnosisItem(name="项4", status=DiagnosisStatus.PASS, description="", suggestion=""),
        ]
        dim = SEODimensionScore(
            name="测试维度",
            score=5.0,
            max_score=10.0,
            items=items,
            status=DiagnosisStatus.PASS,
        )
        assert dim.status == DiagnosisStatus.FAIL


class TestSEODiagnosisResult:
    """诊断结果数据结构测试"""

    def test_create_result(self):
        """测试创建诊断结果"""
        result = SEODiagnosisResult(
            overall_score=75.0,
            dimensions=[],
            recommendations=[],
        )
        assert result.overall_score == 75.0
        assert result.health_level == "good"

    def test_health_level_excellent(self):
        """测试优秀等级"""
        result = SEODiagnosisResult(
            overall_score=85.0,
            dimensions=[],
            recommendations=[],
        )
        assert result.health_level == "excellent"

    def test_health_level_good(self):
        """测试良好等级"""
        result = SEODiagnosisResult(
            overall_score=70.0,
            dimensions=[],
            recommendations=[],
        )
        assert result.health_level == "good"

    def test_health_level_pass(self):
        """测试及格等级"""
        result = SEODiagnosisResult(
            overall_score=50.0,
            dimensions=[],
            recommendations=[],
        )
        assert result.health_level == "pass"

    def test_health_level_danger(self):
        """测试危险等级"""
        result = SEODiagnosisResult(
            overall_score=30.0,
            dimensions=[],
            recommendations=[],
        )
        assert result.health_level == "danger"

    def test_score_clamping(self):
        """测试分数限制"""
        result = SEODiagnosisResult(
            overall_score=150.0,
            dimensions=[],
            recommendations=[],
        )
        assert result.overall_score == 100.0

        result = SEODiagnosisResult(
            overall_score=-10.0,
            dimensions=[],
            recommendations=[],
        )
        assert result.overall_score == 0.0

    def test_to_dict(self):
        """测试字典转换"""
        result = SEODiagnosisResult(
            overall_score=75.0,
            dimensions=[],
            recommendations=[],
        )
        d = result.to_dict()
        assert d["overall_score"] == 75.0
        assert d["health_level"] == "good"
        assert d["health_level_label"] == "良好"
        assert "dimensions" in d
        assert "recommendations" in d


class TestTechnicalSEODiagnosis:
    """技术SEO诊断测试"""

    def test_perfect_technical_seo(self):
        """测试完美技术SEO"""
        data = TechnicalSEOData(
            is_indexed=True,
            crawl_errors=0,
            lcp_seconds=2.0,
            fid_ms=50.0,
            cls_score=0.05,
            has_robots_txt=True,
            robots_txt_blocks_important=False,
            has_sitemap=True,
            sitemap_valid=True,
            url_structure_normalized=True,
        )
        result = diagnose_technical_seo(data)
        assert result.score == result.max_score
        assert result.status == DiagnosisStatus.PASS

    def test_indexed_fail(self):
        """测试未索引情况"""
        data = TechnicalSEOData(is_indexed=False)
        result = diagnose_technical_seo(data)
        assert any(item.status == DiagnosisStatus.FAIL for item in result.items if item.name == "索引状态")

    def test_crawl_errors_warning(self):
        """测试少量爬取错误"""
        data = TechnicalSEOData(crawl_errors=3)
        result = diagnose_technical_seo(data)
        crawl_item = next(item for item in result.items if item.name == "爬取错误")
        assert crawl_item.status == DiagnosisStatus.WARNING

    def test_crawl_errors_fail(self):
        """测试大量爬取错误"""
        data = TechnicalSEOData(crawl_errors=10)
        result = diagnose_technical_seo(data)
        crawl_item = next(item for item in result.items if item.name == "爬取错误")
        assert crawl_item.status == DiagnosisStatus.FAIL

    def test_core_web_vitals_pass(self):
        """测试Core Web Vitals通过"""
        data = TechnicalSEOData(
            lcp_seconds=2.0,
            fid_ms=80.0,
            cls_score=0.05,
        )
        result = diagnose_technical_seo(data)
        cwv_items = [item for item in result.items if item.name in ["LCP", "FID", "CLS"]]
        assert all(item.status == DiagnosisStatus.PASS for item in cwv_items)

    def test_core_web_vitals_warning(self):
        """测试Core Web Vitals警告"""
        data = TechnicalSEOData(
            lcp_seconds=3.0,
            fid_ms=200.0,
            cls_score=0.15,
        )
        result = diagnose_technical_seo(data)
        cwv_items = [item for item in result.items if item.name in ["LCP", "FID", "CLS"]]
        assert any(item.status == DiagnosisStatus.WARNING for item in cwv_items)

    def test_core_web_vitals_fail(self):
        """测试Core Web Vitals失败"""
        data = TechnicalSEOData(
            lcp_seconds=5.0,
            fid_ms=400.0,
            cls_score=0.3,
        )
        result = diagnose_technical_seo(data)
        cwv_items = [item for item in result.items if item.name in ["LCP", "FID", "CLS"]]
        assert all(item.status == DiagnosisStatus.FAIL for item in cwv_items)

    def test_robots_txt_blocks_important(self):
        """测试robots.txt阻止重要页面"""
        data = TechnicalSEOData(
            has_robots_txt=True,
            robots_txt_blocks_important=True,
        )
        result = diagnose_technical_seo(data)
        robots_item = next(item for item in result.items if item.name == "robots.txt")
        assert robots_item.status == DiagnosisStatus.FAIL

    def test_missing_sitemap(self):
        """测试缺少sitemap"""
        data = TechnicalSEOData(has_sitemap=False)
        result = diagnose_technical_seo(data)
        sitemap_item = next(item for item in result.items if item.name == "sitemap")
        assert sitemap_item.status == DiagnosisStatus.FAIL


class TestOnPageSEODiagnosis:
    """页面SEO诊断测试"""

    def test_perfect_on_page_seo(self):
        """测试完美页面SEO"""
        data = OnPageSEOData(
            has_title=True,
            title_length=50,
            title_keyword_stuffing=False,
            has_meta_description=True,
            meta_description_length=140,
            h1_count=1,
            h_structure_valid=True,
            keyword_density=2.0,
            internal_links=10,
            broken_internal_links=0,
            images_without_alt=0,
            total_images=5,
        )
        result = diagnose_on_page_seo(data)
        assert result.score == result.max_score
        assert result.status == DiagnosisStatus.PASS

    def test_missing_title(self):
        """测试缺少Title"""
        data = OnPageSEOData(has_title=False)
        result = diagnose_on_page_seo(data)
        title_item = next(item for item in result.items if item.name == "Title标签")
        assert title_item.status == DiagnosisStatus.FAIL

    def test_title_too_long(self):
        """测试Title过长"""
        data = OnPageSEOData(title_length=80)
        result = diagnose_on_page_seo(data)
        title_item = next(item for item in result.items if item.name == "Title标签")
        assert title_item.status == DiagnosisStatus.WARNING

    def test_keyword_stuffing(self):
        """测试关键词堆砌"""
        data = OnPageSEOData(title_keyword_stuffing=True)
        result = diagnose_on_page_seo(data)
        title_item = next(item for item in result.items if item.name == "Title标签")
        assert title_item.status == DiagnosisStatus.WARNING

    def test_multiple_h1(self):
        """测试多个H1"""
        data = OnPageSEOData(h1_count=3)
        result = diagnose_on_page_seo(data)
        h_item = next(item for item in result.items if item.name == "H标签结构")
        assert h_item.status == DiagnosisStatus.WARNING

    def test_broken_links_warning(self):
        """测试少量死链"""
        data = OnPageSEOData(broken_internal_links=2)
        result = diagnose_on_page_seo(data)
        link_item = next(item for item in result.items if item.name == "内链结构")
        assert link_item.status == DiagnosisStatus.WARNING

    def test_broken_links_fail(self):
        """测试大量死链"""
        data = OnPageSEOData(broken_internal_links=10)
        result = diagnose_on_page_seo(data)
        link_item = next(item for item in result.items if item.name == "内链结构")
        assert link_item.status == DiagnosisStatus.FAIL

    def test_images_without_alt(self):
        """测试图片缺少Alt"""
        data = OnPageSEOData(
            images_without_alt=3,
            total_images=5,
        )
        result = diagnose_on_page_seo(data)
        alt_item = next(item for item in result.items if item.name == "图片Alt文本")
        assert alt_item.status == DiagnosisStatus.FAIL


class TestContentQualityDiagnosis:
    """内容质量诊断测试"""

    def test_perfect_content_quality(self):
        """测试完美内容质量"""
        data = ContentQualityData(
            readability_score=80.0,
            word_count=2000,
            topic_coverage=0.9,
            has_author_info=True,
            has_publication_date=True,
            last_updated_days=10,
            has_citations=True,
            citation_authority=0.9,
            duplicate_content_ratio=0.02,
            has_expert_review=True,
        )
        result = diagnose_content_quality(data)
        assert result.score == result.max_score
        assert result.status == DiagnosisStatus.PASS

    def test_low_readability(self):
        """测试低可读性"""
        data = ContentQualityData(readability_score=40.0)
        result = diagnose_content_quality(data)
        readability_item = next(item for item in result.items if item.name == "可读性")
        assert readability_item.status == DiagnosisStatus.FAIL

    def test_shallow_content(self):
        """测试内容深度不足"""
        data = ContentQualityData(
            word_count=500,
            topic_coverage=0.4,
        )
        result = diagnose_content_quality(data)
        depth_item = next(item for item in result.items if item.name == "信息深度")
        assert depth_item.status == DiagnosisStatus.FAIL

    def test_missing_author(self):
        """测试缺少作者信息"""
        data = ContentQualityData(has_author_info=False)
        result = diagnose_content_quality(data)
        author_item = next(item for item in result.items if item.name == "作者资质")
        assert author_item.status == DiagnosisStatus.WARNING

    def test_stale_content(self):
        """测试过时内容"""
        data = ContentQualityData(last_updated_days=200)
        result = diagnose_content_quality(data)
        freshness_item = next(item for item in result.items if item.name == "内容新鲜度")
        assert freshness_item.status == DiagnosisStatus.FAIL

    def test_high_duplicate_ratio(self):
        """测试高重复内容比例"""
        data = ContentQualityData(duplicate_content_ratio=0.5)
        result = diagnose_content_quality(data)
        duplicate_item = next(item for item in result.items if item.name == "重复内容")
        assert duplicate_item.status == DiagnosisStatus.FAIL


class TestBacklinkDiagnosis:
    """外链分析诊断测试"""

    def test_perfect_backlinks(self):
        """测试完美外链"""
        data = BacklinkData(
            total_backlinks=200,
            referring_domains=50,
            high_authority_links=20,
            toxic_links=0,
            nofollow_ratio=0.3,
            anchor_text_diversity=0.9,
            exact_match_anchor_ratio=0.1,
        )
        result = diagnose_backlinks(data)
        assert result.score == result.max_score
        assert result.status == DiagnosisStatus.PASS

    def test_few_referring_domains(self):
        """测试引用域名少"""
        data = BacklinkData(referring_domains=5)
        result = diagnose_backlinks(data)
        domain_item = next(item for item in result.items if item.name == "引用域名")
        assert domain_item.status == DiagnosisStatus.FAIL

    def test_toxic_links_warning(self):
        """测试少量毒性链接"""
        data = BacklinkData(
            total_backlinks=100,
            toxic_links=3,
        )
        result = diagnose_backlinks(data)
        toxic_item = next(item for item in result.items if item.name == "毒性链接")
        assert toxic_item.status == DiagnosisStatus.WARNING

    def test_toxic_links_fail(self):
        """测试大量毒性链接"""
        data = BacklinkData(
            total_backlinks=50,
            toxic_links=10,
        )
        result = diagnose_backlinks(data)
        toxic_item = next(item for item in result.items if item.name == "毒性链接")
        assert toxic_item.status == DiagnosisStatus.FAIL

    def test_low_anchor_diversity(self):
        """测试锚文本多样性低"""
        data = BacklinkData(
            anchor_text_diversity=0.3,
            exact_match_anchor_ratio=0.6,
        )
        result = diagnose_backlinks(data)
        anchor_item = next(item for item in result.items if item.name == "锚文本分布")
        assert anchor_item.status == DiagnosisStatus.FAIL


class TestUserExperienceDiagnosis:
    """用户体验诊断测试"""

    def test_perfect_ux(self):
        """测试完美用户体验"""
        data = UserExperienceData(
            is_mobile_friendly=True,
            mobile_viewport_set=True,
            page_load_time=1.5,
            has_https=True,
            has_breadcrumbs=True,
            conversion_path_clear=True,
            has_cta=True,
            form_usability=0.95,
            has_search=True,
        )
        result = diagnose_user_experience(data)
        assert result.score == result.max_score
        assert result.status == DiagnosisStatus.PASS

    def test_not_mobile_friendly(self):
        """测试不移动友好"""
        data = UserExperienceData(is_mobile_friendly=False)
        result = diagnose_user_experience(data)
        mobile_item = next(item for item in result.items if item.name == "移动适配")
        assert mobile_item.status == DiagnosisStatus.FAIL

    def test_slow_page_load(self):
        """测试页面加载慢"""
        data = UserExperienceData(page_load_time=5.0)
        result = diagnose_user_experience(data)
        speed_item = next(item for item in result.items if item.name == "页面速度")
        assert speed_item.status == DiagnosisStatus.FAIL

    def test_missing_https(self):
        """测试缺少HTTPS"""
        data = UserExperienceData(has_https=False)
        result = diagnose_user_experience(data)
        https_item = next(item for item in result.items if item.name == "HTTPS")
        assert https_item.status == DiagnosisStatus.FAIL

    def test_missing_cta(self):
        """测试缺少CTA"""
        data = UserExperienceData(has_cta=False)
        result = diagnose_user_experience(data)
        cta_item = next(item for item in result.items if item.name == "CTA")
        assert cta_item.status == DiagnosisStatus.WARNING


class TestRecommendations:
    """优化建议生成测试"""

    def test_generate_recommendations(self):
        """测试建议生成"""
        result = SEODiagnosisResult(
            overall_score=60.0,
            dimensions=[
                SEODimensionScore(
                    name="测试维度",
                    score=10.0,
                    max_score=20.0,
                    items=[
                        DiagnosisItem(
                            name="失败项",
                            status=DiagnosisStatus.FAIL,
                            description="描述",
                            suggestion="修复建议",
                        ),
                        DiagnosisItem(
                            name="警告项",
                            status=DiagnosisStatus.WARNING,
                            description="描述",
                            suggestion="优化建议",
                        ),
                        DiagnosisItem(
                            name="通过项",
                            status=DiagnosisStatus.PASS,
                            description="描述",
                            suggestion="保持",
                        ),
                    ],
                    status=DiagnosisStatus.WARNING,
                ),
            ],
            recommendations=[],
        )

        recommendations = generate_recommendations(result)
        assert len(recommendations) == 2
        assert recommendations[0].priority == "high"
        assert recommendations[1].priority == "medium"

    def test_recommendations_sorted_by_priority(self):
        """测试建议按优先级排序"""
        result = SEODiagnosisResult(
            overall_score=50.0,
            dimensions=[
                SEODimensionScore(
                    name="维度1",
                    score=5.0,
                    max_score=10.0,
                    items=[
                        DiagnosisItem(
                            name="警告项",
                            status=DiagnosisStatus.WARNING,
                            description="",
                            suggestion="",
                        ),
                    ],
                    status=DiagnosisStatus.WARNING,
                ),
                SEODimensionScore(
                    name="维度2",
                    score=5.0,
                    max_score=10.0,
                    items=[
                        DiagnosisItem(
                            name="失败项",
                            status=DiagnosisStatus.FAIL,
                            description="",
                            suggestion="",
                        ),
                    ],
                    status=DiagnosisStatus.WARNING,
                ),
            ],
            recommendations=[],
        )

        recommendations = generate_recommendations(result)
        assert recommendations[0].priority == "high"
        assert recommendations[1].priority == "medium"


class TestSEODiagnosisService:
    """SEO诊断服务测试"""

    @pytest.fixture
    def service(self):
        """创建诊断服务实例"""
        return SEODiagnosisService()

    def test_full_diagnosis_with_defaults(self, service):
        """测试使用默认数据的完整诊断"""
        result = service.diagnose()
        assert isinstance(result, SEODiagnosisResult)
        assert 0 <= result.overall_score <= 100
        assert len(result.dimensions) == 5
        assert isinstance(result.recommendations, list)

    def test_diagnosis_returns_all_dimensions(self, service):
        """测试诊断返回所有维度"""
        result = service.diagnose()
        dimension_names = [dim.name for dim in result.dimensions]
        assert "技术SEO" in dimension_names
        assert "页面SEO" in dimension_names
        assert "内容质量" in dimension_names
        assert "外链分析" in dimension_names
        assert "用户体验" in dimension_names

    def test_diagnosis_with_custom_data(self, service):
        """测试使用自定义数据的诊断"""
        technical_data = TechnicalSEOData(
            is_indexed=False,
            crawl_errors=10,
        )
        result = service.diagnose(technical_data=technical_data)
        assert result.overall_score < 100

    def test_diagnose_technical_only(self, service):
        """测试仅技术SEO诊断"""
        result = service.diagnose_technical_only()
        assert isinstance(result, SEODimensionScore)
        assert result.name == DimensionName.TECHNICAL_SEO

    def test_diagnose_on_page_only(self, service):
        """测试仅页面SEO诊断"""
        result = service.diagnose_on_page_only()
        assert isinstance(result, SEODimensionScore)
        assert result.name == DimensionName.ON_PAGE_SEO

    def test_diagnose_content_only(self, service):
        """测试仅内容质量诊断"""
        result = service.diagnose_content_only()
        assert isinstance(result, SEODimensionScore)
        assert result.name == DimensionName.CONTENT_QUALITY

    def test_diagnose_backlinks_only(self, service):
        """测试仅外链分析"""
        result = service.diagnose_backlinks_only()
        assert isinstance(result, SEODimensionScore)
        assert result.name == DimensionName.BACKLINK_ANALYSIS

    def test_diagnose_ux_only(self, service):
        """测试仅用户体验诊断"""
        result = service.diagnose_ux_only()
        assert isinstance(result, SEODimensionScore)
        assert result.name == DimensionName.USER_EXPERIENCE

    def test_diagnosis_with_poor_data(self, service):
        """测试使用差数据的诊断"""
        technical_data = TechnicalSEOData(
            is_indexed=False,
            crawl_errors=20,
            lcp_seconds=6.0,
            fid_ms=500.0,
            cls_score=0.4,
            has_robots_txt=False,
            has_sitemap=False,
        )
        on_page_data = OnPageSEOData(
            has_title=False,
            has_meta_description=False,
            h1_count=0,
            broken_internal_links=10,
        )
        content_data = ContentQualityData(
            readability_score=30.0,
            word_count=200,
            last_updated_days=365,
            duplicate_content_ratio=0.6,
        )
        backlink_data = BacklinkData(
            referring_domains=2,
            toxic_links=20,
            anchor_text_diversity=0.2,
        )
        ux_data = UserExperienceData(
            is_mobile_friendly=False,
            page_load_time=8.0,
            has_https=False,
        )

        result = service.diagnose(
            technical_data=technical_data,
            on_page_data=on_page_data,
            content_data=content_data,
            backlink_data=backlink_data,
            ux_data=ux_data,
        )

        assert result.overall_score < 30
        assert result.health_level == "danger"
        assert len(result.recommendations) > 0
        assert any(rec.priority == "high" for rec in result.recommendations)

    def test_diagnosis_with_excellent_data(self, service):
        """测试使用优秀数据的诊断"""
        technical_data = TechnicalSEOData(
            is_indexed=True,
            crawl_errors=0,
            lcp_seconds=1.5,
            fid_ms=50.0,
            cls_score=0.03,
        )
        on_page_data = OnPageSEOData(
            title_length=45,
            meta_description_length=140,
            keyword_density=2.0,
        )
        content_data = ContentQualityData(
            readability_score=85.0,
            word_count=2500,
            topic_coverage=0.95,
            has_expert_review=True,
            last_updated_days=5,
        )
        backlink_data = BacklinkData(
            referring_domains=50,
            high_authority_links=20,
            toxic_links=0,
            anchor_text_diversity=0.9,
        )
        ux_data = UserExperienceData(
            page_load_time=1.2,
            form_usability=0.95,
        )

        result = service.diagnose(
            technical_data=technical_data,
            on_page_data=on_page_data,
            content_data=content_data,
            backlink_data=backlink_data,
            ux_data=ux_data,
        )

        assert result.overall_score >= 80
        assert result.health_level == "excellent"

    def test_result_to_dict_format(self, service):
        """测试结果字典格式"""
        result = service.diagnose()
        d = result.to_dict()

        assert "overall_score" in d
        assert "health_level" in d
        assert "health_level_label" in d
        assert "dimensions" in d
        assert "recommendations" in d
        assert isinstance(d["dimensions"], list)
        assert isinstance(d["recommendations"], list)

        if d["dimensions"]:
            dim = d["dimensions"][0]
            assert "name" in dim
            assert "score" in dim
            assert "max_score" in dim
            assert "percentage" in dim
            assert "status" in dim
            assert "items" in dim
