import csv import importlib.util import sys import tempfile import unittest from pathlib import Path from xml.sax.saxutils import escape from zipfile import ZIP_DEFLATED, ZipFile MODULE_PATH = Path(__file__).with_name("export_thirdparty_catalog_xlsx.py") SPEC = importlib.util.spec_from_file_location("thirdparty_export", 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 = [ "Parent SKU", "产品标题", "sku", "变种属性名称一", "变种属性名称二", "变种属性值一", "变种属性值二", "主图(URL)地址", "来源URL", "店铺名", "产品id", "更新时间", ] def inline_cell(column, row, value): return f'{escape(value)}' def make_xlsx(path, data_rows): rows = [] for row_number, values in enumerate([HEADERS, *data_rows], start=1): cells = "".join(inline_cell(chr(65 + index), row_number, value) for index, value in enumerate(values)) rows.append(f'{cells}') worksheet = ( '' '' '' + "".join(rows) + '' ) with ZipFile(path, "w", ZIP_DEFLATED) as archive: archive.writestr("xl/worksheets/sheet1.xml", worksheet) class ThirdPartyCatalogExportTest(unittest.TestCase): def make_source(self, rows): temp = tempfile.TemporaryDirectory() root = Path(temp.name) / "source" source = root / "pinduoduo 域名" / "sample.xlsx" source.parent.mkdir(parents=True) make_xlsx(source, rows) self.addCleanup(temp.cleanup) return root, source, Path(temp.name) / "output" def test_错误_dimension_仍导出完整数据和规范_pdd_url(self): root, source, output = self.make_source([ ["MAIN", "标题", "SKU-A", "颜色分类", "尺码", "黑色", "M", "https://img.example/a.jpg", "https://mobile.pinduoduo.com/goods.html?goods_id=123456789&track=1", "店铺 A", "24948397488", "2026-08-19"], ["MAIN", "标题", "SKU-B", "尺碼", "顏色", "L", "白色", "https://img.example/a.jpg", "https://mobile.yangkeduo.com/goods.html?goods_id=987654321", "店铺 A", "24948397488", "2026-08-19"], ]) result = catalog.export_file(source, root, output) self.assertEqual((result.rows, result.valid_pdd_rows), (2, 2)) with result.output.open(encoding="utf-8-sig", newline="") as handle: exported = list(csv.DictReader(handle)) self.assertEqual(len(exported), 2) self.assertEqual(exported[0]["pdd_goods_url"], "https://mobile.yangkeduo.com/goods.html?goods_id=123456789") self.assertEqual((exported[0]["color"], exported[0]["size"], exported[0]["parse_ok"]), ("黑色", "M", "true")) self.assertEqual((exported[1]["color"], exported[1]["size"]), ("白色", "L")) self.assertEqual(exported[0]["source_file"], "pinduoduo 域名/sample.xlsx") def test_无效_pdd_url_保留蝦皮字段但不创建关联(self): root, source, output = self.make_source([ ["MAIN", "标题", "SKU-A", "款式", "套餐", "甲", "乙", "https://img.example/a.jpg", "https://example.com/item", "店铺 A", "24948397488", ""], ]) result = catalog.export_file(source, root, output) self.assertEqual((result.rows, result.valid_pdd_rows), (1, 0)) with result.output.open(encoding="utf-8-sig", newline="") as handle: exported = next(csv.DictReader(handle)) self.assertEqual(exported["shopee_goods_id"], "24948397488") self.assertEqual(exported["spec_raw"], "甲,乙") self.assertEqual(exported["parse_ok"], "false") self.assertEqual(exported["association_pdd_goods_id"], "") self.assertEqual(exported["validation_code"], "PDD_URL_HOST_INVALID") def test_输出目录不能位于输入目录内(self): root, _, _ = self.make_source([]) args = catalog.build_parser().parse_args([str(root), "--output-dir", str(root / "csv")]) with self.assertRaisesRegex(catalog.CatalogExportError, "输出目录"): catalog.run(args) if __name__ == "__main__": unittest.main()