121 lines
4.2 KiB
Python
121 lines
4.2 KiB
Python
"""批次 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 "<title>" 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
|