import unittest from pathlib import Path from tempfile import TemporaryDirectory from unittest.mock import Mock, patch from upload_video_to_guanghe import ( GuangHeUploader, _match_tm_item_ids_from_product_titles, download_videos, ) class GuangHeMetadataTests(unittest.TestCase): def test_downloads_with_same_attachment_name_are_isolated_by_record(self): attachment = {"file_token": "file-token", "name": "same-name.mp4"} with TemporaryDirectory() as tmp_dir, patch( "upload_video_to_guanghe.DOWNLOAD_DIR", Path(tmp_dir) ), patch("upload_video_to_guanghe.run_lark") as run_lark_mock: def fake_download(args): output_dir = Path(args[args.index("--output") + 1]) output_dir.mkdir(parents=True, exist_ok=True) (output_dir / attachment["name"]).write_bytes(b"video") return 0, "", "" run_lark_mock.side_effect = fake_download first = download_videos("record-one", [attachment]) second = download_videos("record-two", [attachment]) self.assertEqual(first[0].parent.name, "record-one") self.assertEqual(second[0].parent.name, "record-two") self.assertNotEqual(first[0], second[0]) def test_unresolved_style_uses_exact_database_product_title_match(self): result = _match_tm_item_ids_from_product_titles( ["宙斯3", "未建档款"], [ ( "986141740958", "GYXX/光影行星宙斯3双肩包男士背包机能大容量", ), ("655191144133", "GYXX光影行星宙斯双肩包男士电脑包"), ], ) self.assertEqual(result, {"宙斯3": ["986141740958"]}) def test_product_search_term_removes_series_suffix(self): self.assertEqual( GuangHeUploader._normalize_product_search_term("宙斯系列"), "宙斯", ) self.assertEqual( GuangHeUploader._normalize_product_search_term(" 宙斯 系列款 "), "宙斯", ) self.assertEqual( GuangHeUploader._normalize_product_search_term("星迹2"), "星迹2", ) def test_cover_generation_pending_marker_is_not_upload_done(self): self.assertTrue(GuangHeUploader._cover_generation_is_pending("封面生成中")) self.assertFalse(GuangHeUploader._cover_generation_is_pending("智能封面图")) def test_content_label_option_allows_browse_count_but_rejects_longer_label(self): self.assertTrue( GuangHeUploader._content_label_text_matches("# 斜挎包 41673031次浏览", "斜挎包") ) self.assertTrue( GuangHeUploader._content_label_text_matches("商品展示", "商品展示") ) self.assertTrue( GuangHeUploader._content_label_text_matches("#斜挎包", "斜挎包") ) self.assertFalse( GuangHeUploader._content_label_text_matches("# 单肩斜挎包 32871826次浏览", "斜挎包") ) def test_topic_result_card_requires_topic_and_activity_statistics(self): self.assertTrue( GuangHeUploader._topic_result_card_text_matches( "# 在淘宝种草一夏 小二推荐 作品数469.5万 参与数16.2万", "在淘宝种草一夏", ) ) self.assertFalse( GuangHeUploader._topic_result_card_text_matches( "在淘宝种草一夏", "在淘宝种草一夏", ) ) self.assertTrue(GuangHeUploader._topic_card_box_is_clickable(620, 120)) self.assertFalse(GuangHeUploader._topic_card_box_is_clickable(620, 45)) self.assertFalse(GuangHeUploader._topic_card_box_is_clickable(720, 600)) def test_poseidon_edge_uses_authoritative_tote_category(self): self.assertEqual( GuangHeUploader._resolve_title_category( "波塞冬edge", "双肩包、电脑包、托特包、大容量", ), "托特包", ) self.assertEqual( GuangHeUploader._resolve_title_category("星迹2", ""), "斜挎包", ) def test_invalid_online_info_is_rejected_when_main_category_conflicts(self): result = GuangHeUploader._validate_online_product_info( "波塞冬edge", "主包型:双肩包;尺寸:39cm;容量/适配:16寸;材质:尼龙;卖点:分区", ) self.assertEqual(result, "") def test_verified_fallback_info_contains_all_product_fields(self): info = GuangHeUploader._verified_product_info("星迹2") for field in ["主包型:", "尺寸:", "容量/适配:", "材质:", "卖点:"]: self.assertIn(field, info) self.assertIn("斜挎包", info) def test_title_category_uses_name_then_search_features(self): self.assertEqual( GuangHeUploader._resolve_title_category("盖亚斜挎", "轻量、大容量"), "斜挎包", ) self.assertEqual( GuangHeUploader._resolve_title_category("宙斯", "双肩包、大容量、笔电仓"), "双肩包", ) def test_title_normalization_keeps_search_structure_and_uses_computer_bag_term(self): title = GuangHeUploader._normalize_generated_title( "宙斯笔电大容量通勤真能装", ["宙斯"], "双肩包", ) self.assertTrue(title.startswith("宙斯双肩包,")) self.assertTrue(title.endswith("!")) self.assertIn("电脑包", title) self.assertNotIn("笔电", title) self.assertNotIn("笔记本电脑", title) self.assertNotIn("电脑包仓", title) self.assertGreaterEqual(len(title), 26) self.assertLessEqual(len(title), 30) def test_title_normalization_removes_compact_duplicate_model_and_broken_tail(self): title = GuangHeUploader._normalize_generated_title( "光影行星宙斯zair双肩包,宙斯zair,大容量分区收纳,从容应", ["宙斯z air"], "双肩包", ) self.assertTrue(title.startswith("宙斯z air双肩包,")) self.assertEqual(title.replace(" ", "").count("宙斯zair"), 1) self.assertNotIn("从容应!", title) self.assertGreaterEqual(len(title), 26) self.assertLessEqual(len(title), 30) def test_same_style_multiple_videos_get_distinct_titles_when_hermes_repeats(self): uploader = object.__new__(GuangHeUploader) repeated = "光影行星宙斯双肩包科学分区收纳轻量通勤出行" with ( patch.object(uploader, "_search_product_features", return_value=""), patch("upload_video_to_guanghe.HAS_HERMES", True), patch("upload_video_to_guanghe._call_hermes", return_value=(repeated, {})), ): first = uploader._generate_title(["宙斯"], video_index=1, video_total=2) second = uploader._generate_title(["宙斯"], video_index=2, video_total=2) self.assertNotEqual(first, second) self.assertTrue(first.startswith("宙斯双肩包,")) self.assertTrue(second.startswith("宙斯双肩包,")) def test_same_style_repeated_hermes_copy_gets_low_similarity_bodies(self): uploader = object.__new__(GuangHeUploader) repeated = "光影行星宙斯双肩包,科学分区收纳,轻量通勤出行!" with ( patch.object(uploader, "_search_product_features", return_value=""), patch("upload_video_to_guanghe.HAS_HERMES", True), patch("upload_video_to_guanghe._call_hermes", return_value=(repeated, {})), ): titles = [ uploader._generate_title(["宙斯"], video_index=index, video_total=4) for index in range(1, 5) ] bodies = [GuangHeUploader._title_body(title) for title in titles] self.assertEqual(len(set(bodies)), 4) for index, body in enumerate(bodies): for other in bodies[index + 1:]: self.assertLess( GuangHeUploader._title_similarity(body, other), GuangHeUploader.TITLE_SIMILARITY_LIMIT, ) def test_different_styles_cannot_reuse_same_title_skeleton(self): uploader = object.__new__(GuangHeUploader) repeated = "科学分区收纳,轻装通勤出行" with ( patch.object(uploader, "_search_product_features", return_value=""), patch("upload_video_to_guanghe.HAS_HERMES", True), patch("upload_video_to_guanghe._call_hermes", return_value=(repeated, {})), ): first = uploader._generate_title(["宙斯"], video_index=1) second = uploader._generate_title(["星云2"], video_index=1) first_body = GuangHeUploader._title_body(first) second_body = GuangHeUploader._title_body(second) self.assertLess( GuangHeUploader._title_similarity(first_body, second_body), GuangHeUploader.TITLE_SIMILARITY_LIMIT, ) def test_title_similarity_ignores_product_prefix(self): first = "宙斯双肩包,科学分区收纳,轻装通勤出行!" second = "星云2斜挎包,科学分区收纳,轻装通勤出行!" self.assertEqual(GuangHeUploader._title_similarity(first, second), 1.0) def test_full_batch_can_claim_48_globally_distinct_title_bodies(self): uploader = object.__new__(GuangHeUploader) titles = [] for index in range(48): product_names = [f"款式{index % 12 + 1}"] video_index = index // 12 + 1 candidate = uploader._fallback_title( product_names, "双肩包", variant_index=video_index, ) titles.append( uploader._claim_unique_title( candidate, product_names, "双肩包", variant_index=video_index, ) ) self.assertEqual(len(set(titles)), 48) self.assertTrue(all(26 <= len(title) <= 30 for title in titles)) for index, title in enumerate(titles): for other in titles[index + 1:]: self.assertLess( GuangHeUploader._title_similarity(title, other), GuangHeUploader.TITLE_SIMILARITY_LIMIT, ) def test_two_store_uploaders_can_share_one_global_title_registry(self): shared_global = [] shared_styles = {} shared_usage = {} first_uploader = object.__new__(GuangHeUploader) second_uploader = object.__new__(GuangHeUploader) for uploader in (first_uploader, second_uploader): uploader._used_titles_global = shared_global uploader._used_titles_by_products = shared_styles uploader._title_variant_usage = shared_usage first = first_uploader._claim_unique_title( first_uploader._fallback_title(["宙斯"], "双肩包", 1), ["宙斯"], "双肩包", 1, ) second = second_uploader._claim_unique_title( second_uploader._fallback_title(["星云2"], "斜挎包", 1), ["星云2"], "斜挎包", 1, ) self.assertLess( GuangHeUploader._title_similarity(first, second), GuangHeUploader.TITLE_SIMILARITY_LIMIT, ) def test_product_search_specs_prefer_tm_item_ids_and_fallback_to_keyword(self): specs = GuangHeUploader._build_product_search_specs( ["星云2", "未建档系列款"], {"星云2": ["900176117674", "865283623976"]}, ) self.assertEqual( specs, [ { "product_name": "星云2", "search_term": "900176117674", "exact_id": True, }, { "product_name": "未建档系列款", "search_term": "未建档", "exact_id": False, }, { "product_name": "星云2", "search_term": "865283623976", "exact_id": True, }, ], ) def test_exact_id_searches_get_one_store_keyword_fallback(self): specs = GuangHeUploader._build_product_search_specs( ["极星双肩", "未建档款"], {"极星双肩": ["972285625931", "966753757238"]}, ) self.assertEqual( GuangHeUploader._build_exact_search_fallback_specs(specs), [ { "product_name": "极星双肩", "search_term": "极星双肩", "exact_id": False, } ], ) def test_title_fallback_contains_product_category_solution_and_result(self): title = GuangHeUploader._fallback_title(["盖亚斜挎"], "斜挎包") self.assertTrue(title.startswith("盖亚斜挎包,")) self.assertIn(",", title) self.assertTrue(title.endswith("!")) self.assertIn("钥匙卡包各归其位", title) self.assertIn("早高峰取物", title) self.assertGreaterEqual(title.count(","), 2) self.assertGreaterEqual(len(title), 26) self.assertLessEqual(len(title), 30) def test_generated_title_replaces_generic_category_with_model_category(self): uploader = object.__new__(GuangHeUploader) with ( patch.object(uploader, "_search_product_features", return_value=""), patch("upload_video_to_guanghe.HAS_HERMES", True), patch( "upload_video_to_guanghe._call_hermes", return_value=("光影行星宙斯双肩包大容量笔记本电脑轻量通勤", {}), ), ): title = uploader._generate_title(["宙斯"]) self.assertTrue(title.startswith("宙斯双肩包,")) self.assertNotIn("包袋双肩包", title) def test_generated_poseidon_title_cannot_be_changed_to_backpack_by_hermes(self): uploader = object.__new__(GuangHeUploader) with ( patch.object( uploader, "_search_product_features", return_value="双肩包、电脑包、托特包、大容量", ), patch("upload_video_to_guanghe.HAS_HERMES", True), patch( "upload_video_to_guanghe._call_hermes", return_value=( "光影行星波塞冬edge双肩包暴雨通勤防泼水电脑仓从容出行", {}, ), ), ): title = uploader._generate_title(["波塞冬edge"]) self.assertTrue(title.startswith("波塞冬edge托特包,")) self.assertNotIn("双肩包", title) self.assertTrue(title.endswith("!")) self.assertGreaterEqual(len(title), 26) self.assertLessEqual(len(title), 30) def test_primary_content_label_uses_first_product_bag_type(self): self.assertEqual( GuangHeUploader._content_label_for_products(["盖亚斜挎", "极星双肩"]), "斜挎包", ) self.assertEqual( GuangHeUploader._content_label_for_products(["盖亚微单"]), "相机包", ) self.assertEqual( GuangHeUploader._content_label_for_products(["波塞冬edge"]), "托特包", ) self.assertEqual( GuangHeUploader._content_label_for_products(["波塞冬手提电脑包"]), "电脑包", ) def test_catalog_product_titles_supply_style_category_and_verified_info(self): try: GuangHeUploader.configure_catalog_product_titles( { "星云2": [ "GYXX光影行星星云2斜挎包男士单肩包平板胸包", "光影行星星云斜挎包男款邮差单肩包", ], "宙斯": ["GYXX光影行星宙斯双肩包男大容量通勤背包"], "宙斯z air": ["光影行星宙斯zair双肩包电脑包通勤背包"], "黑曜石托特": ["光影行星黑曜石双肩托特多用包"], } ) self.assertEqual( GuangHeUploader._content_label_for_products(["星云2"]), "斜挎包", ) self.assertEqual( GuangHeUploader._resolve_title_category("宙斯"), "双肩包", ) self.assertIn( "主包型:斜挎包", GuangHeUploader._verified_product_info("星云2"), ) self.assertEqual( GuangHeUploader._content_label_for_products(["宙斯z air"]), "双肩包", ) self.assertEqual( GuangHeUploader._content_label_for_products(["黑曜石托特"]), "托特包", ) finally: GuangHeUploader.configure_catalog_product_titles({}) def test_metadata_selects_style_category_and_product_showcase_before_topic(self): cases = [ (["盖亚斜挎"], ["斜挎包", "商品展示"]), (["极星双肩"], ["双肩包", "商品展示"]), ] for product_names, expected_labels in cases: with self.subTest(product_names=product_names): uploader = object.__new__(GuangHeUploader) calls = [] uploader._pick_content_labels = Mock( side_effect=lambda labels: calls.append(("labels", list(labels))) ) uploader._pick_topic = Mock( side_effect=lambda topic: calls.append(("topic", topic)) ) uploader._snapshot = Mock() uploader._prepare_publish_metadata(product_names) self.assertEqual( calls, [ ("labels", expected_labels), ("topic", "在淘宝种草一夏"), ], ) def test_fixed_content_labels_only_keep_product_showcase(self): self.assertEqual(GuangHeUploader.FIXED_CONTENT_LABELS, ("商品展示",)) self.assertNotIn("户外", GuangHeUploader.FIXED_CONTENT_LABELS) def test_fixed_topic_is_not_a_recommended_first_result(self): self.assertEqual(GuangHeUploader.FIXED_TOPIC, "在淘宝种草一夏") if __name__ == "__main__": unittest.main()