Files
cmautobuy/tools/test_import_catalog_xlsx.py
T

256 lines
9.7 KiB
Python
Raw Normal View History

import importlib.util
import json
import sys
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from openpyxl import Workbook
MODULE_PATH = Path(__file__).with_name("import_catalog_xlsx.py")
SPEC = importlib.util.spec_from_file_location("import_catalog_xlsx", MODULE_PATH)
assert SPEC and SPEC.loader
catalog = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = catalog
SPEC.loader.exec_module(catalog)
HEADERS = [
"商品ID",
"商品标题",
"主货号",
"货源URL",
"货源平台",
"货源ID",
"货源店铺名",
"价格",
"币种",
"库存",
"销量",
"浏览量",
"收藏量",
"评论数",
"商品状态",
"店铺显示名",
"平台店铺ID",
"创建时间",
"更新时间",
"缩略图URL",
"变种属性值一",
"变种属性值二",
]
def sample_row(shopee_id, pdd_id, first, second):
return [
shopee_id,
f"商品 {shopee_id}",
"MAIN",
f"https://mobile.yangkeduo.com/goods.html?goods_id={pdd_id}&tracking=removed",
"拼多多",
pdd_id,
"PDD 店铺",
"100",
"TWD",
10,
"",
"",
"",
"",
"NORMAL",
"蝦皮店铺",
"SHOP",
"2026-08-10 08:00:00",
"2026-08-11 09:30:00",
"https://img.example.com/a.jpg",
first,
second,
]
class CatalogXLSXTest(unittest.TestCase):
def make_workbook(self, rows):
temp = tempfile.TemporaryDirectory()
path = Path(temp.name) / "sample.xlsx"
workbook = Workbook()
sheet = workbook.active
sheet.title = "在线商品"
sheet.append(HEADERS)
for row in rows:
sheet.append(row)
workbook.save(path)
workbook.close()
self.addCleanup(temp.cleanup)
return path
def test_聚合规格且不猜颜色尺码并按PDD去重(self):
path = self.make_workbook(
[
sample_row("S-1", "P-1", "黑色", "M"),
sample_row("S-1", "P-1", "白色", "L"),
sample_row("S-2", "P-1", "均码", "红色"),
]
)
plan = catalog.prepare_batches(path, 200, "fill_missing")
self.assertEqual((plan.groups, plan.skus, plan.unique_pdd_products), (2, 3, 1))
self.assertEqual(len(plan.batches), 1)
payload = json.loads(plan.batches[0].body)
self.assertEqual(len(payload["pdd_products"]), 1)
self.assertEqual(payload["pdd_products"][0]["url"], "https://mobile.yangkeduo.com/goods.html?goods_id=P-1")
self.assertEqual(payload["shopee_skus"][2]["spec_raw"], "均码,红色")
self.assertEqual(payload["shopee_skus"][2]["color"], "")
self.assertEqual(payload["shopee_skus"][2]["size"], "")
self.assertFalse(payload["shopee_skus"][2]["parse_ok"])
def test_相同文件重复规划生成完全相同请求(self):
path = self.make_workbook([sample_row("S-1", "P-1", "黑色", "M")])
first = catalog.prepare_batches(path, 200, "fill_missing")
second = catalog.prepare_batches(path, 200, "fill_missing")
self.assertEqual(first.batches[0].batch_id, second.batches[0].batch_id)
self.assertEqual(first.batches[0].body, second.batches[0].body)
changed_policy = catalog.prepare_batches(path, 200, "insert_only")
changed_size = catalog.prepare_batches(path, 1, "fill_missing")
self.assertNotEqual(first.batches[0].batch_id, changed_policy.batches[0].batch_id)
self.assertNotEqual(first.batches[0].batch_id, changed_size.batches[0].batch_id)
def test_正式化模式支持正向和反向两列(self):
path = self.make_workbook(
[
sample_row("S-1", "P-1", "黑色", "M码"),
sample_row("S-1", "P-1", "白色", "L码"),
sample_row("S-2", "P-2", "小码", "红色"),
sample_row("S-2", "P-2", "大码", "蓝色"),
]
)
plan = catalog.prepare_batches(path, 200, "fill_missing", True)
payload = json.loads(plan.batches[0].body)
skus = payload["shopee_skus"]
self.assertEqual((plan.formalized_groups, plan.formalized_skus), (2, 4))
self.assertEqual((plan.pending_groups, plan.pending_skus), (0, 0))
self.assertEqual(payload["shopee_products"], [])
self.assertEqual(payload["pdd_products"], [])
self.assertEqual(payload["associations"], [])
self.assertEqual((skus[0]["color"], skus[0]["size"]), ("黑色", "M码"))
self.assertEqual((skus[2]["color"], skus[2]["size"]), ("红色", "小码"))
self.assertTrue(all(sku["parse_ok"] for sku in skus))
def test_正式化模式支持确定的单维规格(self):
path = self.make_workbook(
[
sample_row("S-1", "P-1", "均码【40-60公斤】", ""),
sample_row("S-2", "P-2", "黑色", ""),
]
)
plan = catalog.prepare_batches(path, 200, "fill_missing", True)
payload = json.loads(plan.batches[0].body)
by_goods = {sku["goods_id"]: sku for sku in payload["shopee_skus"]}
self.assertEqual(by_goods["S-1"]["size"], "均码【40-60公斤】")
self.assertEqual(by_goods["S-1"]["color"], "")
self.assertEqual(by_goods["S-2"]["color"], "黑色")
self.assertEqual(by_goods["S-2"]["size"], "")
def test_正式化模式不提交歧义商品(self):
path = self.make_workbook(
[
sample_row("S-1", "P-1", "款式甲", "套餐甲"),
sample_row("S-1", "P-1", "款式乙", "套餐乙"),
sample_row("S-2", "P-2", "黑色", "M码"),
]
)
plan = catalog.prepare_batches(path, 200, "fill_missing", True)
payload = json.loads(plan.batches[0].body)
self.assertEqual((plan.groups, plan.skus), (2, 3))
self.assertEqual((plan.formalized_groups, plan.formalized_skus), (1, 1))
self.assertEqual((plan.pending_groups, plan.pending_skus), (1, 2))
self.assertEqual(
{sku["goods_id"] for sku in payload["shopee_skus"]}, {"S-2"}
)
def test_同一商品列数不一致时整体保持待补(self):
path = self.make_workbook(
[
sample_row("S-1", "P-1", "黑色", "M码"),
sample_row("S-1", "P-1", "白色", ""),
]
)
plan = catalog.prepare_batches(path, 200, "fill_missing", True)
self.assertEqual((plan.formalized_groups, plan.pending_groups), (0, 1))
self.assertEqual(plan.batches, ())
self.assertIn(
("歧义:同一商品规格列数不一致", 1), plan.decision_counts
)
def test_另一列也出现尺码证据时不猜测(self):
path = self.make_workbook(
[
sample_row("S-1", "P-1", "小码", "黑色"),
sample_row("S-1", "P-1", "大码", "白色均码"),
]
)
plan = catalog.prepare_batches(path, 200, "fill_missing", True)
self.assertEqual((plan.formalized_groups, plan.pending_groups), (0, 1))
self.assertEqual(plan.batches, ())
self.assertIn(("歧义:无法唯一确定尺码列", 1), plan.decision_counts)
def test_正式化模式固定fill_missing且使用独立批次版本(self):
path = self.make_workbook([sample_row("S-1", "P-1", "黑色", "M码")])
raw = catalog.prepare_batches(path, 200, "fill_missing")
formal = catalog.prepare_batches(path, 200, "fill_missing", True)
self.assertTrue(raw.batches[0].batch_id.startswith("xlsx-v1-"))
self.assertTrue(formal.batches[0].batch_id.startswith("xlsx-spec-v1-"))
self.assertNotEqual(raw.batches[0].batch_id, formal.batches[0].batch_id)
with self.assertRaisesRegex(catalog.CatalogImportError, "fill_missing"):
catalog.prepare_batches(path, 200, "overwrite_same_source", True)
def test_商品不跨批且批次满足限制(self):
rows = []
for product in range(3):
rows.extend(
sample_row(f"S-{product}", f"P-{product}", f"颜色-{sku}", "M")
for sku in range(3)
)
path = self.make_workbook(rows)
plan = catalog.prepare_batches(path, 2, "fill_missing")
self.assertEqual([batch.shopee_products for batch in plan.batches], [2, 1])
self.assertEqual([batch.shopee_skus for batch in plan.batches], [6, 3])
for batch in plan.batches:
self.assertLessEqual(batch.shopee_products + batch.pdd_products, 500)
self.assertLessEqual(batch.shopee_skus, 5000)
self.assertLessEqual(len(batch.body), 5 << 20)
def test_重复规格明确停止(self):
row = sample_row("S-1", "P-1", "黑色", "M")
path = self.make_workbook([row, row])
with self.assertRaisesRegex(catalog.CatalogImportError, "规格重复"):
catalog.prepare_batches(path, 200, "fill_missing")
def test_临时接口错误重试且不改变请求(self):
batch = catalog.PreparedBatch("B-1", b"{}", 1, 1, 1, 1)
success = {"batch_id": "B-1", "status": "succeeded"}
with patch.object(
catalog,
"post_batch",
side_effect=[
catalog.CatalogHTTPError(503, "TEMPORARY", "稍后重试", True),
success,
],
) as post:
got = catalog.submit_with_retry(
"https://example.com/api", "secret", batch, 10, 1, sleep=lambda _: None
)
self.assertEqual(got, success)
self.assertEqual(post.call_count, 2)
self.assertEqual(post.call_args_list[0].args[2], post.call_args_list[1].args[2])
if __name__ == "__main__":
unittest.main()