This commit is contained in:
2026-07-27 10:34:53 +08:00
commit 3527975794
74 changed files with 16405 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
"""乐天商家(店铺)解析与请求转换测试"""
import json
from pathlib import Path
import pytest
from app.core.errors import InvalidRequestError, ScrapeParseError
from app.models.scrape import ShopItemsRequest, SortOption
from app.parsers.shop import parse_shop_detail
from app.utils.urls import build_search_url, build_shop_url, split_shop_url
FIXTURES = Path(__file__).parent / "fixtures"
@pytest.fixture
def shop_state() -> dict:
return json.loads((FIXTURES / "shop_state.json").read_text(encoding="utf-8"))
@pytest.fixture
def shop_html() -> str:
return (FIXTURES / "shop_page.html").read_text(encoding="utf-8")
# ---- 解析 ----
def test_parse_shop_detail_reads_state_and_logo(shop_state, shop_html):
detail = parse_shop_detail(shop_state, shop_code="edion", html=shop_html)
assert detail.shop_id == 272415
assert detail.shop_code == "edion"
assert detail.shop_name == "エディオン 楽天市場店"
assert detail.review_score == 4.51
assert detail.review_count == 128136
assert detail.review_displayed is True
assert detail.is_39_shop is True
assert detail.status == 1
assert detail.introduction
assert detail.signboard_url.startswith("https://")
# logo 不在 state 里,只能从页面的 ld+json 微数据取
assert detail.logo_url.startswith("https://thumbnail.image.rakuten.co.jp/")
def test_parse_shop_detail_normalizes_shop_url(shop_state):
"""state 里的 shopUrl 是 http 且不带尾斜杠,对外统一成规范形式"""
detail = parse_shop_detail(shop_state, shop_code="edion")
assert detail.shop_url == "https://www.rakuten.co.jp/edion/"
def test_parse_shop_detail_without_html_leaves_logo_empty(shop_state):
assert parse_shop_detail(shop_state, shop_code="edion").logo_url == ""
def test_parse_shop_detail_rejects_page_without_shop_node():
with pytest.raises(ScrapeParseError):
parse_shop_detail({"state": {}}, shop_code="edion")
# ---- URL ----
def test_shop_url_round_trip():
assert split_shop_url(build_shop_url("edion")) == "edion"
def test_split_shop_url_rejects_reserved_paths():
"""/category/ 之类的站点自有路径不是店铺页,别把 category 当成店铺代码"""
with pytest.raises(InvalidRequestError):
split_shop_url("https://www.rakuten.co.jp/category/101205/")
def test_split_shop_url_rejects_foreign_host():
with pytest.raises(InvalidRequestError):
split_shop_url("https://item.rakuten.co.jp/edion/123/")
# ---- 商家商品:转成限定店铺的搜索 ----
def test_shop_items_request_converts_to_shop_scoped_search():
payload = ShopItemsRequest(
shop_code="edion", page=2, keyword="テレビ", sort=SortOption.PRICE_ASC, max_price=50000
)
search = payload.to_search_request(272415)
assert search.shop_id == 272415
assert search.keyword == "テレビ"
assert search.page == 2
url = build_search_url(search)
assert "sid=272415" in url
assert "p=2" in url
assert "s=2" in url # price_asc
assert "max=50000" in url
def test_shop_items_request_needs_an_identifier():
with pytest.raises(ValueError):
ShopItemsRequest()
def test_search_accepts_shop_id_alone_as_target():
"""只按店铺取商品是合法查询:站点的 sid-only 搜索能正常返回"""
url = build_search_url(ShopItemsRequest(shop_id=272415).to_search_request(272415))
assert "sid=272415" in url