"""批次 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