"""批次 C3 · 营销官网测试。
覆盖:页面可达、SEO meta/结构化数据、价格页数据与 Plan 同源、行业页只写已实现特性
("不写纸面功能")、移动端 viewport。
"""
import pytest
from django.test import Client
from apps.billing.models import Plan, seed_plans
@pytest.fixture
def site(db):
seed_plans()
return Client()
PAGES = [
("/site/", "AI 开单"),
("/site/pricing/", "价格与功能对比"),
("/site/industry/food/", "食品饮料"),
("/site/industry/hardware/", "五金建材"),
("/site/industry/autoparts/", "汽配"),
]
@pytest.mark.parametrize("url,needle", PAGES)
def test_pages_reachable(site, url, needle):
resp = site.get(url)
assert resp.status_code == 200, url
html = resp.content.decode("utf-8")
assert needle in html
@pytest.mark.parametrize("url,_needle", PAGES)
def test_pages_have_seo_meta(site, url, _needle):
"""每页都要有 title / description / canonical / 结构化数据 / viewport。"""
html = site.get(url).content.decode("utf-8")
assert "
" in html
assert 'name="description"' in html
assert 'rel="canonical"' in html
assert 'application/ld+json' in html
assert 'name="viewport"' in html
assert "width=device-width" in html
def test_pricing_reads_plan_limits(site):
"""价格页的额度数字必须来自 Plan.limits(与系统校验同源)。"""
html = site.get("/site/pricing/").content.decode("utf-8")
free = Plan.objects.get(code="free")
basic = Plan.objects.get(code="basic")
pro = Plan.objects.get(code="pro")
assert free.name in html and basic.name in html and pro.name in html
assert "998" in html and "4800" in html
# 免费版 100 商品 / 基础版 3000 商品
assert f"{free.limits['products']:,}" in html
assert f"{basic.limits['products']:,}" in html
# 专业版不限项展示为"不限"
assert "不限" in html
def test_pricing_flag_row_uses_checkmark(site):
html = site.get("/site/pricing/").content.decode("utf-8")
assert "✓" in html # 支持的项打勾
assert "—" in html # 不支持的项破折号(free 无订货商城)
def test_home_lists_three_ai_sellpoints(site):
html = site.get("/site/").content.decode("utf-8")
assert "AI 应收风险预警" in html
assert "AI 录单" in html
assert "AI 经营问答" in html
# 独家卖点有标注
assert "独家" in html
def test_home_lists_six_roles(site):
html = site.get("/site/").content.decode("utf-8")
for role in ["老板", "财务", "销售", "仓管", "采购", "运营"]:
assert role in html, role
def test_food_page_claims_only_shipped_features(site):
"""食品页卖点必须都是已交付的:批次/FEFO/临期预警/多单位/信用额度。"""
html = site.get("/site/industry/food/").content.decode("utf-8")
for kw in ["批次账", "FEFO", "临期自动预警", "多单位换算", "信用额度"]:
assert kw in html, kw
def test_hardware_page_claims_only_shipped_features(site):
html = site.get("/site/industry/hardware/").content.decode("utf-8")
for kw in ["换算率", "三档抹零", "最低售价管控", "价格历史参考"]:
assert kw in html, kw
def test_autoparts_page_marks_unshipped_as_planned(site):
"""未交付的 SN 追溯必须明确标注"规划中",不得写成已有功能。"""
html = site.get("/site/industry/autoparts/").content.decode("utf-8")
assert "规划中" in html
assert "序列号" in html
# 明确说明尚未交付
assert "尚未交付" in html or "路线图" in html
def test_demo_cta_links_present(site):
"""每页都要有试用入口(转化路径)。"""
for url, _ in PAGES:
html = site.get(url).content.decode("utf-8")
assert "免费试用" in html or "免注册体验" in html, url
def test_site_does_not_shadow_spa(site):
"""官网挂在 /site/ 下,不能抢掉 SPA 的根路径。"""
resp = site.get("/")
assert resp.status_code == 200
# 根路径返回 SPA(FileResponse 流式返回 index.html),不含官网导航
body = b"".join(resp.streaming_content).decode("utf-8", errors="ignore")
assert "site/pricing" not in body