Merge patrol delete tests and remove pycache artifacts

This commit is contained in:
koko
2026-05-01 22:46:11 +08:00
parent 7ba916f00c
commit 7c2b154c20
7 changed files with 233 additions and 94 deletions

View File

@@ -185,6 +185,32 @@ def test_wait_until_client_ready_polls_until_available(monkeypatch):
assert sleeps == [base.CLIENT_READY_INTERVAL, base.CLIENT_READY_INTERVAL] assert sleeps == [base.CLIENT_READY_INTERVAL, base.CLIENT_READY_INTERVAL]
def test_ziniao_webdriver_tail_redacts_password(monkeypatch, tmp_path):
appdata = tmp_path / "AppData" / "Roaming"
log_dir = appdata / "ziniaobrowser" / "instances" / "userdata1" / "logs" / "client"
log_dir.mkdir(parents=True)
log_file = log_dir / "webdriver.20260501.log"
log_file.write_text(
'[error] {"action":"updateCore","username":"u","password":"secret"}\n',
encoding="utf-8",
)
messages = []
monkeypatch.setenv("APPDATA", str(appdata))
monkeypatch.setattr(base.time, "strftime", lambda fmt: "20260501")
sink_id = base.logger.add(messages.append, level="ERROR", format="{message}")
try:
base.ZiniaoDriver({})._log_ziniao_webdriver_tail()
finally:
base.logger.remove(sink_id)
output = "\n".join(messages)
assert "webdriver.20260501.log" in output
assert '"password":"***"' in output
assert "secret" not in output
def test_close_store_success_uses_current_store(monkeypatch): def test_close_store_success_uses_current_store(monkeypatch):
payloads = [] payloads = []

View File

@@ -105,7 +105,7 @@ def test_patrol_delete_task_post_result_retries_business_failure(monkeypatch):
assert len(calls) == 3 assert len(calls) == 3
def test_normalize_country_sections_keeps_only_status_and_quantity_for_complete_draft_rows(): def test_normalize_country_sections_omits_status_fields_for_complete_draft_rows():
rows = PatrolDeleteTask._normalize_country_sections_for_result( rows = PatrolDeleteTask._normalize_country_sections_for_result(
[ [
{ {
@@ -132,7 +132,7 @@ def test_normalize_country_sections_keeps_only_status_and_quantity_for_complete_
{ {
"country": "意大利", "country": "意大利",
"rows": [ "rows": [
{"status": "商品信息草稿", "quantity": "12"}, {"quantity": "12", "deleteQuantity": "3"},
{"status": "缺少的信息", "quantity": "1", "deleteQuantity": "1", "processStatus": "已完成"}, {"status": "缺少的信息", "quantity": "1", "deleteQuantity": "1", "processStatus": "已完成"},
], ],
} }
@@ -143,15 +143,15 @@ def test_replace_complete_draft_rows_uses_quick_view_rows():
country_section = { country_section = {
"country": "西班牙", "country": "西班牙",
"rows": [ "rows": [
{"status": "全部", "quantity": "6230", "deleteQuantity": "无需删除", "processStatus": "无可删商品"}, {"status": "全部", "quantity": "6230", "deleteQuantity": "0", "processStatus": "无可删商品"},
{"status": "商品信息草稿", "quantity": "", "deleteQuantity": "0", "processStatus": ""}, {"status": "商品信息草稿", "quantity": "", "deleteQuantity": "0", "processStatus": ""},
], ],
} }
complete_draft_section = { complete_draft_section = {
"country": "西班牙", "country": "西班牙",
"rows": [ "rows": [
{"type": "completeDraft", "status": "未提交的草稿", "quantity": "1"}, {"type": "completeDraft", "quantity": "1", "deleteQuantity": "1"},
{"type": "completeDraft", "status": "已提交:提供缺少的信息", "quantity": "0"}, {"type": "completeDraft", "quantity": "0", "deleteQuantity": "0"},
], ],
} }
@@ -160,18 +160,18 @@ def test_replace_complete_draft_rows_uses_quick_view_rows():
assert merged == { assert merged == {
"country": "西班牙", "country": "西班牙",
"rows": [ "rows": [
{"status": "全部", "quantity": "6230", "deleteQuantity": "无需删除", "processStatus": "无可删商品"}, {"status": "全部", "quantity": "6230", "deleteQuantity": "0", "processStatus": "无可删商品"},
{"type": "completeDraft", "status": "未提交的草稿", "quantity": "1"}, {"type": "completeDraft", "quantity": "1", "deleteQuantity": "1"},
{"type": "completeDraft", "status": "已提交:提供缺少的信息", "quantity": "0"}, {"type": "completeDraft", "quantity": "0", "deleteQuantity": "0"},
], ],
} }
assert PatrolDeleteTask._normalize_country_sections_for_result([merged]) == [ assert PatrolDeleteTask._normalize_country_sections_for_result([merged]) == [
{ {
"country": "西班牙", "country": "西班牙",
"rows": [ "rows": [
{"status": "全部", "quantity": "6230", "deleteQuantity": "无需删除", "processStatus": "无可删商品"}, {"status": "全部", "quantity": "6230", "deleteQuantity": "0", "processStatus": "无可删商品"},
{"status": "未提交的草稿", "quantity": "1"}, {"quantity": "1", "deleteQuantity": "1"},
{"status": "已提交:提供缺少的信息", "quantity": "0"}, {"quantity": "0", "deleteQuantity": "0"},
], ],
} }
] ]
@@ -194,7 +194,7 @@ def test_patrol_delete_task_process_task_aggregates_country_results(monkeypatch)
def fake_open_shop(self, **kwargs): def fake_open_shop(self, **kwargs):
return Driver() return Driver()
def fake_process_country(self, driver, task_id, shop_name, country_name): def fake_process_country(self, driver, task_id, shop_name, country_name, delete_conditions=None):
if country_name == "德国": if country_name == "德国":
return { return {
"success": True, "success": True,
@@ -308,6 +308,90 @@ def test_patrol_delete_task_process_task_skips_missing_required_data(monkeypatch
assert calls == [] assert calls == []
def test_patrol_delete_task_process_task_passes_delete_conditions(monkeypatch):
from config import runing_shop, runing_task
runing_task.clear()
runing_shop.clear()
seen_conditions = []
class Driver:
def close_store(self):
return None
def fake_open_shop(self, **kwargs):
return Driver()
def fake_process_country(self, driver, task_id, shop_name, country_name, delete_conditions=None):
seen_conditions.append(delete_conditions)
return {
"success": True,
"countrySection": {
"country": country_name,
"rows": [{"status": "全部", "quantity": "", "deleteQuantity": "0", "processStatus": "无可删商品"}],
},
"cartRatio": {"country": country_name, "ratio": ""},
"deletedCount": 0,
"reopenRequired": False,
}
monkeypatch.setattr(PatrolDeleteTask, "open_shop", fake_open_shop)
monkeypatch.setattr(PatrolDeleteTask, "process_country", fake_process_country)
monkeypatch.setattr(PatrolDeleteTask, "post_result", lambda self, **kwargs: None)
PatrolDeleteTask().process_task(
{
"type": "patrol-delete-run",
"data": {
"taskId": 7,
"items": [{"shopName": "郭亚芳", "companyName": "rongchuang123"}],
"country_sections": [{"country": "德国", "rows": [{"status": "全部"}]}],
"delete_conditions": [{"id": 1, "conditionText": "商品信息草稿、补全草稿"}],
},
}
)
assert seen_conditions == [["商品信息草稿", "补全草稿"]]
def test_delete_complete_draft_country_section_reports_delete_quantity(monkeypatch):
class Driver:
def delete_complete_drafts_from_non_whitelisted_tags(self, shop_name, country_name, **kwargs):
return {
"draftRows": [
{"tag": "未提交的草稿", "quantity": 0},
{"tag": "已提交:提供缺少的信息", "quantity": 2},
],
"deleteCounts": {"未提交的草稿": 3},
"totalDeleted": 3,
}
result = PatrolDeleteTask()._delete_complete_draft_country_section_safe(
Driver(),
"郭亚芳",
"德国",
["未提交的草稿"],
)
assert result["deletedCount"] == 3
assert result["countrySection"] == {
"country": "德国",
"rows": [
{"type": "completeDraft", "status": "未提交的草稿", "quantity": "0", "deleteQuantity": "3"},
{"type": "completeDraft", "status": "已提交:提供缺少的信息", "quantity": "2", "deleteQuantity": "0"},
],
}
assert PatrolDeleteTask._normalize_country_sections_for_result([result["countrySection"]]) == [
{
"country": "德国",
"rows": [
{"quantity": "0", "deleteQuantity": "3"},
{"quantity": "2", "deleteQuantity": "0"},
],
}
]
def test_patrol_delete_task_reports_shop_error(monkeypatch): def test_patrol_delete_task_reports_shop_error(monkeypatch):
from config import runing_shop, runing_task from config import runing_shop, runing_task
@@ -377,7 +461,11 @@ def test_delete_all_listings_from_non_whitelisted_statuses_deletes_each_row_in_s
monkeypatch.setattr(driver, "_delete_selected_filtered_inventory_rows", fake_delete_selected_filtered_inventory_rows) monkeypatch.setattr(driver, "_delete_selected_filtered_inventory_rows", fake_delete_selected_filtered_inventory_rows)
monkeypatch.setattr(driver, "_wait_inventory_loader", lambda timeout=5: None) monkeypatch.setattr(driver, "_wait_inventory_loader", lambda timeout=5: None)
result = driver.delete_all_listings_from_non_whitelisted_statuses("郭亚芳", "德国") result = driver.delete_all_listings_from_non_whitelisted_statuses(
"郭亚芳",
"德国",
delete_conditions=["商品信息草稿"],
)
assert state["selected"] == ["商品信息草稿"] assert state["selected"] == ["商品信息草稿"]
assert state["deleted"] == 2 assert state["deleted"] == 2
@@ -388,6 +476,66 @@ def test_delete_all_listings_from_non_whitelisted_statuses_deletes_each_row_in_s
assert [item["target"]["sku"] for item in result["results"]] == ["bulk-selected"] assert [item["target"]["sku"] for item in result["results"]] == ["bulk-selected"]
def test_delete_all_listings_from_non_whitelisted_statuses_respects_allowed_statuses(monkeypatch):
driver = InventoryManage({})
state = {"selected": [], "deleted": 0}
def fake_read_listing_status_rows_for_delete(shop_name, country, **kwargs):
return [
{"status": "商品信息草稿", "quantity": "2", "deleteQuantity": "", "processStatus": ""},
{"status": "缺少的信息", "quantity": "3", "deleteQuantity": "", "processStatus": ""},
{"status": "全部", "quantity": "9", "deleteQuantity": "", "processStatus": ""},
]
def fake_select_listing_status(status, **kwargs):
state["selected"].append(status)
def fake_delete_selected_filtered_inventory_rows(status, **kwargs):
state["deleted"] += 1
return {
"deletedCount": 1,
"target": {"sku": status, "rawText": status},
"successMessage": f"{status} 删除成功",
}
monkeypatch.setattr(driver, "_read_listing_status_rows_for_delete", fake_read_listing_status_rows_for_delete)
monkeypatch.setattr(driver, "select_listing_status", fake_select_listing_status)
monkeypatch.setattr(driver, "_delete_selected_filtered_inventory_rows", fake_delete_selected_filtered_inventory_rows)
monkeypatch.setattr(driver, "_wait_inventory_loader", lambda timeout=5: None)
result = driver.delete_all_listings_from_non_whitelisted_statuses(
"郭亚芳",
"德国",
allowed_statuses=["缺少的信息"],
)
assert state["selected"] == ["缺少的信息"]
assert state["deleted"] == 3
assert result["deleteCounts"] == {"缺少的信息": 3}
def test_delete_all_listings_from_non_whitelisted_statuses_empty_allowed_statuses_deletes_nothing(monkeypatch):
driver = InventoryManage({})
state = {"selected": []}
monkeypatch.setattr(
driver,
"_read_listing_status_rows_for_delete",
lambda shop_name, country, **kwargs: [{"status": "商品信息草稿", "quantity": "2"}],
)
monkeypatch.setattr(driver, "select_listing_status", lambda status, **kwargs: state["selected"].append(status))
result = driver.delete_all_listings_from_non_whitelisted_statuses(
"郭亚芳",
"德国",
allowed_statuses=[],
)
assert state["selected"] == []
assert result["deleteCandidates"] == []
assert result["totalDeleted"] == 0
def test_select_listing_status_uses_dropdown_option_not_visible_status(monkeypatch): def test_select_listing_status_uses_dropdown_option_not_visible_status(monkeypatch):
driver = InventoryManage({}) driver = InventoryManage({})
calls = [] calls = []
@@ -606,7 +754,7 @@ def test_build_deletable_status_rows_keeps_non_whitelisted_statuses():
{"status": "全部", "quantity": "9"}, {"status": "全部", "quantity": "9"},
] ]
assert InventoryManage._build_deletable_status_rows(rows) == [ assert InventoryManage._build_deletable_status_rows(rows, ["库存状态删除"]) == [
{"status": "商品信息草稿", "canonicalStatus": "商品信息草稿", "quantity": 2}, {"status": "商品信息草稿", "canonicalStatus": "商品信息草稿", "quantity": 2},
{"status": "自定义异常状态", "canonicalStatus": "自定义异常状态", "quantity": 5}, {"status": "自定义异常状态", "canonicalStatus": "自定义异常状态", "quantity": 5},
] ]
@@ -621,7 +769,7 @@ def test_build_deletable_status_rows_skips_group_labels_without_quantity():
{"status": "全部", "quantity": ""}, {"status": "全部", "quantity": ""},
] ]
assert InventoryManage._build_deletable_status_rows(rows) == [ assert InventoryManage._build_deletable_status_rows(rows, ["库存状态删除"]) == [
{"status": "缺少的信息", "canonicalStatus": "缺少的信息", "quantity": 1}, {"status": "缺少的信息", "canonicalStatus": "缺少的信息", "quantity": 1},
] ]
@@ -644,74 +792,65 @@ def test_build_deletable_status_rows_keeps_legacy_non_whitelist_statuses_deletab
{"status": "订单页面已删除", "quantity": "3"}, {"status": "订单页面已删除", "quantity": "3"},
] ]
assert InventoryManage._build_deletable_status_rows(rows) == [ assert InventoryManage._build_deletable_status_rows(rows, ["库存状态删除"]) == [
{"status": "详情页面已删除", "canonicalStatus": "详情页面已删除", "quantity": 1}, {"status": "详情页面已删除", "canonicalStatus": "详情页面已删除", "quantity": 1},
{"status": "缺少的信息", "canonicalStatus": "缺少的信息", "quantity": 2}, {"status": "缺少的信息", "canonicalStatus": "缺少的信息", "quantity": 2},
{"status": "订单页面已删除", "canonicalStatus": "详情页面已删除", "quantity": 3}, {"status": "订单页面已删除", "canonicalStatus": "详情页面已删除", "quantity": 3},
] ]
def test_build_deletable_status_rows_filters_by_delete_conditions():
rows = [
{"status": "商品信息草稿", "quantity": "2"},
{"status": "缺少的信息", "quantity": "5"},
{"status": "全部", "quantity": "9"},
]
assert InventoryManage._build_deletable_status_rows(rows, ["商品信息草稿"]) == [
{"status": "商品信息草稿", "canonicalStatus": "商品信息草稿", "quantity": 2},
]
assert InventoryManage._build_deletable_status_rows(rows, []) == []
def test_build_deletable_complete_draft_tag_rows_respects_complete_draft_condition():
rows = [
{"tag": "未提交的草稿", "quantity": 1},
{"tag": "已提交:提供缺少的信息", "quantity": 2},
]
assert InventoryManage._build_deletable_complete_draft_tag_rows(rows, ["已提交:提供缺少的信息"]) == [
{"tag": "已提交:提供缺少的信息", "canonicalTag": "已提交:提供缺少的信息", "quantity": 2},
]
assert InventoryManage._build_deletable_complete_draft_tag_rows(rows, ["补全草稿"]) == [
{"tag": "未提交的草稿", "canonicalTag": "未提交的草稿", "quantity": 1},
{"tag": "已提交:提供缺少的信息", "canonicalTag": "已提交:提供缺少的信息", "quantity": 2},
]
assert InventoryManage._build_deletable_complete_draft_tag_rows(rows, []) == []
def test_parse_delete_condition_texts_accepts_frontend_payload_shapes():
assert PatrolDeleteTask._parse_delete_condition_texts(
[
{"id": 1, "conditionText": "商品信息草稿、缺少的信息"},
{"id": 2, "condition_text": "补全草稿"},
"详情页面已删除",
]
) == ["商品信息草稿", "缺少的信息", "补全草稿", "详情页面已删除"]
def test_resolve_delete_condition_texts_defaults_to_empty_list_when_missing():
assert PatrolDeleteTask._resolve_delete_condition_texts({}) == []
def test_is_delete_success_message_accepts_both_copy_variants(): def test_is_delete_success_message_accepts_both_copy_variants():
assert InventoryManage._is_delete_success_message("1 个商品已经删除。所作更改需要15分钟才会显示在商品详情页面上") assert InventoryManage._is_delete_success_message("1 个商品已经删除。所作更改需要15分钟才会显示在商品详情页面上")
assert InventoryManage._is_delete_success_message("所做更改需要 15 分钟才会显示在商品详情页面上。 1 个商品已删除。") assert InventoryManage._is_delete_success_message("所做更改需要 15 分钟才会显示在商品详情页面上。 1 个商品已删除。")
def test_is_delete_success_message_accepts_deleted_count_without_delay_notice():
assert InventoryManage._is_delete_success_message("1 个商品已删除。")
assert InventoryManage._is_delete_success_message("已删除 2 个商品。")
assert not InventoryManage._is_delete_success_message("0 个商品已删除。")
def test_is_delete_success_message_accepts_deleted_without_exact_count():
assert InventoryManage._is_delete_success_message("一个或多个商品已删除。")
assert InventoryManage._is_delete_success_message("一个或多个商品已经删除")
assert InventoryManage._extract_delete_success_count("一个或多个商品已删除。") == 0
def test_extract_delete_success_count_accepts_success_message(): def test_extract_delete_success_count_accepts_success_message():
assert InventoryManage._extract_delete_success_count("所做更改需要 15 分钟才会显示在商品详情页面上。 3 个商品已删除。") == 3 assert InventoryManage._extract_delete_success_count("所做更改需要 15 分钟才会显示在商品详情页面上。 3 个商品已删除。") == 3
def test_short_error_simplifies_delete_success_message_timeout():
message = (
"状态[详情页面已删除]删除后未读取到成功提示: "
"['所做更改需要 15 分钟才会显示在商品详情页面上。 一个或多个商品已删除', "
"'莆田市城厢区从奇偶贸易...']"
)
assert PatrolDeleteTask._short_error(message) == "删除后未读取到成功提示"
def test_short_error_simplifies_common_patrol_delete_failures():
examples = {
"检测到任务 12 的暂停请求,停止处理": "任务已中断",
"用户取消任务": "任务已中断",
"与页面的连接已断开: target frame detached": "浏览器连接中断",
"切换国家 德国 失败: element not found": "切换国家失败",
"未找到可用商品状态下拉框: {'ok': False, 'text': '...'}": "未找到商品状态下拉框",
"商品状态下拉框已打开但未读取到选项: {'ok': True}": "未读取到商品状态下拉选项",
"未找到商品状态选项[详情页面已删除]: {'rows': ['...']}": "未找到商品状态选项",
"状态[详情页面已删除]全选商品失败: {'ok': False, 'reason': '...'}": "全选商品失败",
"状态[详情页面已删除]点击批量删除失败: {'menuOpened': False, 'html': '...'}": "点击批量删除失败",
"状态[详情页面已删除]未出现删除确认弹窗": "未出现删除确认弹窗",
"当前页面对象不支持执行 JavaScript无法探测 Shadow DOM": "当前页面无法执行 JavaScript",
}
for message, expected in examples.items():
assert PatrolDeleteTask._short_error(message) == expected
def test_build_country_section_result_uses_simplified_delete_timeout_failure_reason():
section = PatrolDeleteTask._build_country_section_result(
country_name="英国",
status_rows=[{"status": "详情页面已删除", "quantity": "1"}],
delete_counts={},
default_process_status=f"处理失败: {PatrolDeleteTask._short_error('状态[详情页面已删除]删除后未读取到成功提示: []')}",
)
assert section["rows"][0]["processStatus"] == "处理失败: 删除后未读取到成功提示"
def test_build_country_section_result_sets_zero_delete_quantity_when_no_deletable_products(): def test_build_country_section_result_sets_zero_delete_quantity_when_no_deletable_products():
section = PatrolDeleteTask._build_country_section_result( section = PatrolDeleteTask._build_country_section_result(
country_name="德国", country_name="德国",
@@ -740,32 +879,6 @@ def test_build_country_section_result_sets_zero_delete_quantity_for_empty_no_del
} }
def test_build_country_section_result_marks_all_whitelisted_statuses_as_no_delete_needed():
whitelist_statuses = [
"全部",
"定价问题",
"需要批准",
"在搜索结果中禁止显示",
"配送问题",
"缺少报价",
"已停售",
"不可售",
"在售",
]
section = PatrolDeleteTask._build_country_section_result(
country_name="爱尔兰",
status_rows=[{"status": status, "quantity": "1"} for status in whitelist_statuses]
+ [{"status": "缺少的信息", "quantity": "2"}],
delete_counts={"缺少的信息": 2},
)
rows_by_status = {row["status"]: row for row in section["rows"]}
for status in whitelist_statuses:
assert rows_by_status[status]["deleteQuantity"] == "无需删除"
assert rows_by_status["缺少的信息"]["deleteQuantity"] == "2"
def test_parse_label_quantity_text_accepts_complete_draft_sample(): def test_parse_label_quantity_text_accepts_complete_draft_sample():
assert InventoryManage._parse_label_quantity_text("未提交的草稿 (0)") == ("未提交的草稿", 0) assert InventoryManage._parse_label_quantity_text("未提交的草稿 (0)") == ("未提交的草稿", 0)