""" GEO诊断服务单元测试 测试6大维度诊断逻辑、评分算法、推荐生成和服务类 """ import pytest from app.services.geo_diagnosis import ( GEODiagnosisService, GEODiagnosisInput, diagnose_content_extractability, diagnose_entity_clarity, diagnose_eeat_signals, diagnose_schema_markup, diagnose_topic_authority, diagnose_citation_readiness, generate_recommendations, get_health_level, get_health_level_label, ) class TestContentExtractability: """内容可提取性诊断测试""" def test_all_pass(self): """所有项都通过""" result = diagnose_content_extractability( has_direct_answer=True, has_qa_headings=True, has_structured_data=True, has_internal_links=True, has_freshness_info=True, update_days_ago=10, ) assert result.score == 20.0 assert result.max_score == 20.0 assert result.status == "pass" assert result.percentage == 100.0 def test_all_fail(self): """所有项都失败""" result = diagnose_content_extractability( has_direct_answer=False, has_qa_headings=False, has_structured_data=False, has_internal_links=False, has_freshness_info=False, ) assert result.score == 0.0 assert result.status == "warning" def test_partial_pass(self): """部分通过""" result = diagnose_content_extractability( has_direct_answer=True, has_qa_headings=True, has_structured_data=False, has_internal_links=False, has_freshness_info=False, ) assert result.score == 11.0 # 6 + 5 def test_freshness_recent(self): """内容新鲜度 - 近期更新""" result = diagnose_content_extractability( has_freshness_info=True, update_days_ago=10, ) freshness_item = [i for i in result.items if i.name == "内容新鲜度"][0] assert freshness_item.score == 2.0 assert freshness_item.status == "pass" def test_freshness_old(self): """内容新鲜度 - 过期更新""" result = diagnose_content_extractability( has_freshness_info=True, update_days_ago=100, ) freshness_item = [i for i in result.items if i.name == "内容新鲜度"][0] assert freshness_item.score == 0.5 assert freshness_item.status == "warning" class TestEntityClarity: """实体清晰度诊断测试""" def test_all_pass(self): """所有项都通过""" result = diagnose_entity_clarity( has_brand_definition=True, has_target_audience=True, has_unique_value=True, has_industry_classification=True, ) assert result.score == 15.0 assert result.max_score == 15.0 assert result.status == "pass" def test_all_fail(self): """所有项都失败""" result = diagnose_entity_clarity( has_brand_definition=False, has_target_audience=False, has_unique_value=False, has_industry_classification=False, ) assert result.score == 0.0 assert result.status == "warning" def test_partial_pass(self): """部分通过""" result = diagnose_entity_clarity( has_brand_definition=True, has_target_audience=True, has_unique_value=False, has_industry_classification=False, ) assert result.score == 9.0 # 5 + 4 class TestEEATSignals: """E-E-A-T信号诊断测试""" def test_all_pass(self): """所有项都通过""" result = diagnose_eeat_signals( has_author_bio=True, author_credentials_complete=1.0, has_certifications=True, certification_count=5, has_data_sources=True, authoritative_source_ratio=1.0, has_expert_endorsements=True, endorsement_count=5, ) assert result.score == 20.0 assert result.max_score == 20.0 assert result.status == "pass" def test_all_fail(self): """所有项都失败""" result = diagnose_eeat_signals( has_author_bio=False, has_certifications=False, has_data_sources=False, has_expert_endorsements=False, ) assert result.score == 0.0 assert result.status == "warning" def test_author_partial(self): """作者资质部分完成""" result = diagnose_eeat_signals( has_author_bio=True, author_credentials_complete=0.5, ) author_item = [i for i in result.items if i.name == "作者资质"][0] assert author_item.score == 3.0 # 0.5 * 6.0 assert author_item.status == "warning" def test_certification_tiers(self): """认证数量分级测试""" # 5个以上 result = diagnose_eeat_signals(has_certifications=True, certification_count=5) cert_item = [i for i in result.items if i.name == "专业认证"][0] assert cert_item.score == 5.0 # 3-4个 result = diagnose_eeat_signals(has_certifications=True, certification_count=3) cert_item = [i for i in result.items if i.name == "专业认证"][0] assert cert_item.score == 4.0 # 1-2个 result = diagnose_eeat_signals(has_certifications=True, certification_count=1) cert_item = [i for i in result.items if i.name == "专业认证"][0] assert cert_item.score == 2.5 class TestSchemaMarkup: """Schema标记诊断测试""" def test_all_pass(self): """所有项都通过""" result = diagnose_schema_markup( has_organization=True, has_product=True, has_article=True, has_faq=True, has_howto=True, has_breadcrumb=True, ) assert result.score == 15.0 assert result.max_score == 15.0 assert result.status == "pass" def test_all_fail(self): """所有项都失败""" result = diagnose_schema_markup( has_organization=False, has_product=False, has_article=False, has_faq=False, has_howto=False, has_breadcrumb=False, ) assert result.score == 0.0 assert result.status == "warning" def test_p0_only(self): """仅P0必须项""" result = diagnose_schema_markup( has_organization=True, has_product=True, has_article=True, ) assert result.score == 10.0 # 4 + 3 + 3 assert result.status == "pass" def test_schema_count(self): """Schema计数""" result = diagnose_schema_markup( has_organization=True, has_product=True, ) assert result.detail["schema_count"] == 2 class TestTopicAuthority: """主题权威诊断测试""" def test_all_pass(self): """所有项都通过""" result = diagnose_topic_authority( content_depth_score=1.0, topic_coverage_ratio=1.0, entity_consistency_score=1.0, cluster_completeness=1.0, ) assert result.score == 15.0 assert result.max_score == 15.0 assert result.status == "pass" def test_all_fail(self): """所有项都失败""" result = diagnose_topic_authority( content_depth_score=0.0, topic_coverage_ratio=0.0, entity_consistency_score=0.0, cluster_completeness=0.0, ) assert result.score == 0.0 assert result.status == "warning" def test_partial_scores(self): """部分分数""" result = diagnose_topic_authority( content_depth_score=0.8, topic_coverage_ratio=0.5, entity_consistency_score=0.7, cluster_completeness=0.4, ) # 0.8*5 + 0.5*4 + 0.7*3 + 0.4*3 = 4 + 2 + 2.1 + 1.2 = 9.3 assert result.score == pytest.approx(9.3, rel=0.01) class TestCitationReadiness: """引用就绪度诊断测试""" def test_all_pass(self): """所有项都通过""" result = diagnose_citation_readiness( answer_ownership_rate=0.6, citation_accuracy=1.0, ai_sov=0.35, competitor_gap=0.0, ) assert result.score == 15.0 assert result.max_score == 15.0 assert result.status == "pass" def test_all_fail(self): """所有项都失败""" result = diagnose_citation_readiness( answer_ownership_rate=0.0, citation_accuracy=0.0, ai_sov=0.0, competitor_gap=0.6, ) assert result.score == 0.0 assert result.status == "warning" def test_aor_tiers(self): """AOR分级测试""" # >= 50% result = diagnose_citation_readiness(answer_ownership_rate=0.5) aor_item = [i for i in result.items if i.name == "引用频率 (AOR)"][0] assert aor_item.score == 5.0 # 30-49% result = diagnose_citation_readiness(answer_ownership_rate=0.3) aor_item = [i for i in result.items if i.name == "引用频率 (AOR)"][0] assert aor_item.score == 3.5 # 10-29% result = diagnose_citation_readiness(answer_ownership_rate=0.1) aor_item = [i for i in result.items if i.name == "引用频率 (AOR)"][0] assert aor_item.score == 2.0 def test_competitor_gap_tiers(self): """竞品差距分级测试""" # <= 10pp result = diagnose_citation_readiness(competitor_gap=0.05) gap_item = [i for i in result.items if i.name == "竞品对比"][0] assert gap_item.score == 3.0 # 10-20pp result = diagnose_citation_readiness(competitor_gap=0.15) gap_item = [i for i in result.items if i.name == "竞品对比"][0] assert gap_item.score == 2.0 class TestRecommendations: """推荐生成测试""" def test_generate_from_fail_items(self): """从fail项生成P0推荐""" dimensions = [ diagnose_content_extractability( has_direct_answer=False, has_qa_headings=False, ), ] recommendations = generate_recommendations(dimensions) assert len(recommendations) >= 2 # 检查有P0推荐(不一定是全部) p0_recs = [r for r in recommendations if r.priority == "P0"] assert len(p0_recs) >= 2 def test_generate_from_warning_items(self): """从warning项生成P1推荐""" dimensions = [ diagnose_content_extractability( has_direct_answer=True, has_qa_headings=True, has_structured_data=True, has_internal_links=False, has_freshness_info=True, update_days_ago=50, ), ] recommendations = generate_recommendations(dimensions) p1_recs = [r for r in recommendations if r.priority == "P1"] assert len(p1_recs) >= 1 def test_priority_ordering(self): """推荐按优先级排序""" dimensions = [ diagnose_content_extractability( has_direct_answer=False, has_qa_headings=False, has_structured_data=True, has_internal_links=False, has_freshness_info=True, update_days_ago=50, ), ] recommendations = generate_recommendations(dimensions) priorities = [r.priority for r in recommendations] assert priorities == sorted(priorities) def test_empty_dimensions(self): """空维度列表""" recommendations = generate_recommendations([]) assert len(recommendations) == 0 class TestHealthLevel: """健康等级测试""" def test_excellent(self): assert get_health_level(85) == "excellent" assert get_health_level(80) == "excellent" def test_good(self): assert get_health_level(70) == "good" assert get_health_level(60) == "good" def test_pass(self): assert get_health_level(50) == "pass" assert get_health_level(40) == "pass" def test_danger(self): assert get_health_level(30) == "danger" assert get_health_level(0) == "danger" def test_labels(self): assert get_health_level_label("excellent") == "优秀" assert get_health_level_label("good") == "良好" assert get_health_level_label("pass") == "及格" assert get_health_level_label("danger") == "危险" class TestGEODiagnosisService: """GEO诊断服务类测试""" @pytest.fixture def service(self): return GEODiagnosisService() def test_full_diagnosis_all_pass(self, service): """完整诊断 - 所有项通过""" input_data = GEODiagnosisInput( # 内容可提取性 has_direct_answer=True, has_qa_headings=True, has_structured_data=True, has_internal_links=True, has_freshness_info=True, update_days_ago=10, # 实体清晰度 has_brand_definition=True, has_target_audience=True, has_unique_value=True, has_industry_classification=True, # E-E-A-T has_author_bio=True, author_credentials_complete=1.0, has_certifications=True, certification_count=5, has_data_sources=True, authoritative_source_ratio=1.0, has_expert_endorsements=True, endorsement_count=5, # Schema has_organization=True, has_product=True, has_article=True, has_faq=True, has_howto=True, has_breadcrumb=True, # 主题权威 content_depth_score=1.0, topic_coverage_ratio=1.0, entity_consistency_score=1.0, cluster_completeness=1.0, # 引用就绪度 answer_ownership_rate=0.6, citation_accuracy=1.0, ai_sov=0.35, competitor_gap=0.0, ) result = service.diagnose(input_data) assert result.overall_score == 100.0 assert result.health_level == "excellent" assert len(result.dimensions) == 6 assert len(result.recommendations) == 0 def test_full_diagnosis_all_fail(self, service): """完整诊断 - 所有项失败""" input_data = GEODiagnosisInput() result = service.diagnose(input_data) # 由于有些项在默认情况下会得少量分数,总分不一定是0 assert result.overall_score < 10.0 assert result.health_level == "danger" assert len(result.dimensions) == 6 assert len(result.recommendations) > 0 def test_diagnose_from_dict(self, service): """从字典执行诊断""" data = { "has_direct_answer": True, "has_brand_definition": True, "has_author_bio": True, "author_credentials_complete": 0.8, "has_organization": True, "content_depth_score": 0.8, "answer_ownership_rate": 0.5, } result = service.diagnose_from_dict(data) assert result.overall_score > 0 assert len(result.dimensions) == 6 def test_result_to_dict(self, service): """结果转字典""" input_data = GEODiagnosisInput( has_direct_answer=True, has_brand_definition=True, ) result = service.diagnose(input_data) result_dict = result.to_dict() assert "overall_score" in result_dict assert "health_level" in result_dict assert "health_level_label" in result_dict assert "dimensions" in result_dict assert "recommendations" in result_dict assert len(result_dict["dimensions"]) == 6 def test_score_boundaries(self, service): """评分边界测试""" # 最低分 result = service.diagnose(GEODiagnosisInput()) assert result.overall_score >= 0.0 # 最高分 input_data = GEODiagnosisInput( has_direct_answer=True, has_qa_headings=True, has_structured_data=True, has_internal_links=True, has_freshness_info=True, update_days_ago=10, has_brand_definition=True, has_target_audience=True, has_unique_value=True, has_industry_classification=True, has_author_bio=True, author_credentials_complete=1.0, has_certifications=True, certification_count=5, has_data_sources=True, authoritative_source_ratio=1.0, has_expert_endorsements=True, endorsement_count=5, has_organization=True, has_product=True, has_article=True, has_faq=True, has_howto=True, has_breadcrumb=True, content_depth_score=1.0, topic_coverage_ratio=1.0, entity_consistency_score=1.0, cluster_completeness=1.0, answer_ownership_rate=0.6, citation_accuracy=0.95, ai_sov=0.35, competitor_gap=0.05, ) result = service.diagnose(input_data) assert result.overall_score <= 100.0 def test_health_levels(self, service): """健康等级测试""" # excellent (>= 80) input_data = GEODiagnosisInput( has_direct_answer=True, has_qa_headings=True, has_structured_data=True, has_internal_links=True, has_freshness_info=True, update_days_ago=10, has_brand_definition=True, has_target_audience=True, has_unique_value=True, has_industry_classification=True, has_author_bio=True, author_credentials_complete=0.9, has_certifications=True, certification_count=4, has_data_sources=True, authoritative_source_ratio=0.9, has_expert_endorsements=True, endorsement_count=4, has_organization=True, has_product=True, has_article=True, has_faq=True, has_howto=True, has_breadcrumb=True, content_depth_score=0.9, topic_coverage_ratio=0.9, entity_consistency_score=0.9, cluster_completeness=0.8, answer_ownership_rate=0.6, citation_accuracy=0.95, ai_sov=0.35, competitor_gap=0.05, ) result = service.diagnose(input_data) assert result.health_level == "excellent" def test_dimension_scores_sum(self, service): """维度分数求和验证""" input_data = GEODiagnosisInput( has_direct_answer=True, has_brand_definition=True, ) result = service.diagnose(input_data) # 各维度分数求和应等于总分 total = sum(dim.score for dim in result.dimensions) assert result.overall_score == pytest.approx(total, rel=0.01) def test_recommendations_generated(self, service): """推荐生成验证""" input_data = GEODiagnosisInput() result = service.diagnose(input_data) # 所有项都失败时应该有推荐 assert len(result.recommendations) > 0 assert all(r.priority in ["P0", "P1", "P2"] for r in result.recommendations)