- apps/core/blocks.py 新增 ProductCardBlock/PricingBlock/TimelineBlock/TeamBlock/TechStackBlock/VideoBlock,COMMON_BLOCKS 扩充至 14 个内容 Block - TeamBlock 新增 TeamMemberChooserBlock(SnippetChooserBlock),复用 FormBlock 已有的 get_api_representation 展开模式,展开 TeamMember 完整字段(含 serialize_image 复用) - 为 blog/cases/home/products/solutions 五个 Page 模型生成并应用 alter field body 迁移(StreamField block 定义变更会写入迁移 state) - apps/core/tests.py 新增 TeamMemberChooserBlock 空值/展开单元测试,pytest 33 个用例全部通过 - 设计文档 §2.6/§2.16 同步更新为已实现
218 lines
7.7 KiB
Python
218 lines
7.7 KiB
Python
"""apps.core 基础单元测试:SEOablePage 抽象基类字段与公共 Block。"""
|
|
import pytest
|
|
from rest_framework.test import APIClient
|
|
|
|
from apps.core.blocks import FormBlock, TeamMemberChooserBlock
|
|
from apps.core.models import (
|
|
NavigationMenu,
|
|
NavigationMenuItem,
|
|
Partner,
|
|
SimpleContentPage,
|
|
SiteSettings,
|
|
TeamMember,
|
|
Testimonial,
|
|
)
|
|
from apps.forms.models import FormDefinition, FormDefinitionField
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
def test_seoable_page_fields_available_on_subclass(home_page):
|
|
"""SEOablePage 是抽象基类,其 SEO 字段应出现在具体子类(HomePage)上。"""
|
|
home_page.seo_title_override = "自定义标题"
|
|
home_page.seo_description_override = "自定义描述"
|
|
home_page.canonical_url = "https://example.com/"
|
|
home_page.schema_json = {"@type": "Organization"}
|
|
home_page.save()
|
|
|
|
home_page.refresh_from_db()
|
|
assert home_page.seo_title_override == "自定义标题"
|
|
assert home_page.seo_description_override == "自定义描述"
|
|
assert home_page.canonical_url == "https://example.com/"
|
|
assert home_page.schema_json == {"@type": "Organization"}
|
|
|
|
|
|
def test_form_block_api_representation_expands_fields():
|
|
"""FormBlock.get_api_representation 应展开 FormDefinition 及其子字段,
|
|
而不是仅返回 SnippetChooserBlock 默认的主键。"""
|
|
form_def = FormDefinition.objects.create(
|
|
name="联系我们",
|
|
submit_button_text="立即提交",
|
|
success_message="已收到您的信息",
|
|
)
|
|
FormDefinitionField.objects.create(
|
|
form=form_def,
|
|
label="姓名",
|
|
field_key="name",
|
|
field_type="text",
|
|
required=True,
|
|
)
|
|
FormDefinitionField.objects.create(
|
|
form=form_def,
|
|
label="意向",
|
|
field_key="interest",
|
|
field_type="select",
|
|
required=False,
|
|
choices="产品咨询, 合作洽谈",
|
|
)
|
|
|
|
block = FormBlock()
|
|
result = block.get_api_representation({"form": form_def})
|
|
|
|
assert result["form"]["id"] == form_def.pk
|
|
assert result["form"]["name"] == "联系我们"
|
|
assert result["form"]["submit_button_text"] == "立即提交"
|
|
assert len(result["form"]["fields"]) == 2
|
|
assert result["form"]["fields"][1]["choices"] == ["产品咨询", "合作洽谈"]
|
|
|
|
|
|
def test_form_block_api_representation_handles_empty_form():
|
|
block = FormBlock()
|
|
result = block.get_api_representation({"form": None})
|
|
assert result == {"form": None}
|
|
|
|
|
|
def test_simple_content_page_can_be_created_under_home(home_page):
|
|
"""隐私政策等法务页面应可作为 HomePage 的子页面创建,并保留富文本内容。"""
|
|
page = SimpleContentPage(
|
|
title="隐私政策",
|
|
slug="privacy-policy",
|
|
body="<p>我们重视您的个人信息保护。</p>",
|
|
)
|
|
home_page.add_child(instance=page)
|
|
|
|
page.refresh_from_db()
|
|
assert SimpleContentPage.objects.live().descendant_of(home_page).count() == 1
|
|
assert "个人信息保护" in page.body
|
|
|
|
|
|
def test_team_member_default_ordering():
|
|
"""团队成员应按 order 字段升序排列。"""
|
|
TeamMember.objects.create(name="李四", order=2)
|
|
TeamMember.objects.create(name="张三", order=1)
|
|
names = list(TeamMember.objects.values_list("name", flat=True))
|
|
assert names == ["张三", "李四"]
|
|
|
|
|
|
def test_testimonial_str_includes_author_and_quote():
|
|
testimonial = Testimonial.objects.create(
|
|
quote="非常专业的服务,效果超出预期。", author_name="王经理"
|
|
)
|
|
assert "王经理" in str(testimonial)
|
|
|
|
|
|
def test_partner_str_returns_name():
|
|
partner = Partner.objects.create(name="示例合作伙伴")
|
|
assert str(partner) == "示例合作伙伴"
|
|
|
|
|
|
def test_navigation_menu_item_url_prefers_external_link_over_page(home_page):
|
|
menu = NavigationMenu.objects.create(name="main")
|
|
item = NavigationMenuItem.objects.create(
|
|
menu=menu, label="首页", link_page=home_page, link_url="https://example.com"
|
|
)
|
|
assert item.url == "https://example.com"
|
|
|
|
|
|
def test_navigation_menu_item_url_falls_back_to_page_url(home_page):
|
|
menu = NavigationMenu.objects.create(name="main")
|
|
item = NavigationMenuItem.objects.create(menu=menu, label="首页", link_page=home_page)
|
|
assert item.url == home_page.url
|
|
|
|
|
|
def test_navigation_menu_item_url_defaults_to_hash_when_empty():
|
|
menu = NavigationMenu.objects.create(name="main")
|
|
item = NavigationMenuItem.objects.create(menu=menu, label="占位")
|
|
assert item.url == "#"
|
|
|
|
|
|
def test_site_settings_for_site_creates_singleton_with_defaults():
|
|
from wagtail.models import Site
|
|
|
|
site = Site.objects.get(is_default_site=True)
|
|
settings_obj = SiteSettings.for_site(site)
|
|
assert settings_obj.icp_url == "https://beian.miit.gov.cn/"
|
|
# 再次获取应复用同一条记录而非重复创建
|
|
assert SiteSettings.for_site(site).pk == settings_obj.pk
|
|
|
|
|
|
def test_team_api_returns_ordered_members():
|
|
TeamMember.objects.create(name="李四", role="工程师", order=2)
|
|
TeamMember.objects.create(name="张三", role="总经理", order=1)
|
|
|
|
client = APIClient()
|
|
response = client.get("/api/v1/custom/core/team/")
|
|
|
|
assert response.status_code == 200
|
|
items = response.data["items"]
|
|
assert [item["name"] for item in items] == ["张三", "李四"]
|
|
assert items[0]["role"] == "总经理"
|
|
|
|
|
|
def test_testimonial_api_returns_list():
|
|
Testimonial.objects.create(quote="效果很好", author_name="客户A")
|
|
|
|
client = APIClient()
|
|
response = client.get("/api/v1/custom/core/testimonials/")
|
|
|
|
assert response.status_code == 200
|
|
assert response.data["items"][0]["author_name"] == "客户A"
|
|
|
|
|
|
def test_team_member_chooser_block_api_representation_expands_fields():
|
|
"""TeamMemberChooserBlock.get_api_representation 应展开 TeamMember 的完整字段,
|
|
而不是仅返回 SnippetChooserBlock 默认的主键。"""
|
|
member = TeamMember.objects.create(name="王五", role="产品经理", bio="十年行业经验")
|
|
|
|
block = TeamMemberChooserBlock()
|
|
result = block.get_api_representation(member)
|
|
|
|
assert result["id"] == member.pk
|
|
assert result["name"] == "王五"
|
|
assert result["role"] == "产品经理"
|
|
assert result["bio"] == "十年行业经验"
|
|
assert result["photo"] is None
|
|
|
|
|
|
def test_team_member_chooser_block_api_representation_handles_none():
|
|
block = TeamMemberChooserBlock()
|
|
assert block.get_api_representation(None) is None
|
|
|
|
|
|
def test_partner_api_returns_list():
|
|
Partner.objects.create(name="伙伴A", website_url="https://partner-a.example.com")
|
|
|
|
client = APIClient()
|
|
response = client.get("/api/v1/custom/core/partners/")
|
|
|
|
assert response.status_code == 200
|
|
assert response.data["items"][0]["name"] == "伙伴A"
|
|
|
|
|
|
def test_navigation_menu_api_returns_items_by_name(home_page):
|
|
menu = NavigationMenu.objects.create(name="main")
|
|
NavigationMenuItem.objects.create(menu=menu, label="首页", link_page=home_page, sort_order=0)
|
|
NavigationMenuItem.objects.create(menu=menu, label="外部链接", link_url="https://example.com", sort_order=1)
|
|
|
|
client = APIClient()
|
|
response = client.get("/api/v1/custom/core/navigation/?name=main")
|
|
|
|
assert response.status_code == 200
|
|
assert response.data["name"] == "main"
|
|
assert len(response.data["items"]) == 2
|
|
assert response.data["items"][1]["url"] == "https://example.com"
|
|
|
|
|
|
def test_navigation_menu_api_404_when_missing():
|
|
client = APIClient()
|
|
response = client.get("/api/v1/custom/core/navigation/?name=does-not-exist")
|
|
assert response.status_code == 404
|
|
|
|
|
|
def test_site_settings_api_returns_defaults():
|
|
client = APIClient()
|
|
response = client.get("/api/v1/custom/core/site-settings/")
|
|
|
|
assert response.status_code == 200
|
|
assert response.data["icp_url"] == "https://beian.miit.gov.cn/"
|