fix: stabilize Shopee cover replacement
This commit is contained in:
+113
-6
@@ -169,6 +169,8 @@ class FakeCoverCDP:
|
||||
upload_input_ready=True,
|
||||
restore_upload_after_delete=False,
|
||||
page_error_toasts=None,
|
||||
upload_tile_click=True,
|
||||
require_upload_tile_click=False,
|
||||
):
|
||||
self.initial = cover_rects(count)
|
||||
self.after_delete = cover_rects(max(0, count - 1), prefix="kept", start=1)
|
||||
@@ -189,10 +191,14 @@ class FakeCoverCDP:
|
||||
self.upload_input_ready = upload_input_ready
|
||||
self.restore_upload_after_delete = restore_upload_after_delete
|
||||
self.page_error_toasts = page_error_toasts or []
|
||||
self.upload_tile_click = upload_tile_click
|
||||
self.require_upload_tile_click = require_upload_tile_click
|
||||
self.delete_clicked = False
|
||||
self.confirm_clicked = False
|
||||
self.upload_tile_clicked = False
|
||||
self.uploaded = False
|
||||
self.uploaded_after_stable = None
|
||||
self.upload_tile_clicked_before_file_set = None
|
||||
self.dragged = False
|
||||
self.sent = []
|
||||
self.drag_calls = []
|
||||
@@ -244,6 +250,12 @@ class FakeCoverCDP:
|
||||
return json.dumps(self._state(busy_count=1))
|
||||
if self.uploaded and self.upload_mode == "error":
|
||||
return json.dumps(self._state(errors=["圖片格式錯誤"]))
|
||||
if self.uploaded and self.upload_mode == "duplicate":
|
||||
state = self._state()
|
||||
state["toasts"] = ["有1張重複的圖片"]
|
||||
state["error_toasts"] = ["有1張重複的圖片"]
|
||||
state["page_error_toasts"] = ["有1張重複的圖片"]
|
||||
return json.dumps(state)
|
||||
if self.confirm_clicked and not self.uploaded and self.post_delete_busy_polls > 0:
|
||||
self.post_delete_busy_polls -= 1
|
||||
return json.dumps(self._state(busy_count=1))
|
||||
@@ -258,14 +270,22 @@ class FakeCoverCDP:
|
||||
return json.dumps({"clicked": False, "reason": "NO_CONFIRM_BUTTON"})
|
||||
self.confirm_clicked = True
|
||||
return json.dumps({"clicked": True, "reason": None, "text": "刪除"})
|
||||
if expr == editor.JS_CLICK_UPLOAD_TILE:
|
||||
if not self.upload_tile_click:
|
||||
return json.dumps({"clicked": False, "reason": "NO_UPLOAD_TILE"})
|
||||
self.upload_tile_clicked = True
|
||||
return json.dumps({"clicked": True, "reason": None})
|
||||
return True
|
||||
|
||||
def object_id(self, expr):
|
||||
if self.require_upload_tile_click and not self.upload_tile_clicked:
|
||||
return None
|
||||
return "upload-input" if self._upload_ready() else None
|
||||
|
||||
def send(self, method, params=None):
|
||||
self.sent.append((method, params or {}))
|
||||
if method == "DOM.setFileInputFiles":
|
||||
self.upload_tile_clicked_before_file_set = self.upload_tile_clicked
|
||||
self.uploaded_after_stable = self.post_delete_busy_polls == 0
|
||||
self.uploaded = True
|
||||
return {}
|
||||
@@ -577,7 +597,9 @@ class EditorLoginTests(unittest.TestCase):
|
||||
) as change_title, mock.patch("app.editor.replace_cover") as replace_cover, mock.patch(
|
||||
"app.editor.click_update",
|
||||
return_value={"clicked": True, "reason": None},
|
||||
), mock.patch("app.editor.close_tab", return_value=True) as close_tab:
|
||||
), mock.patch("app.editor.close_tab", return_value=True) as close_tab, mock.patch(
|
||||
"app.editor.time.sleep"
|
||||
) as sleep:
|
||||
result = editor.apply_task(
|
||||
{"debug_port": 9222},
|
||||
{"item_id": "51100639510", "new_title": "新标题"},
|
||||
@@ -588,6 +610,7 @@ class EditorLoginTests(unittest.TestCase):
|
||||
change_title.assert_called_once_with(cdp, "新标题")
|
||||
replace_cover.assert_not_called()
|
||||
self.assertTrue(cdp.closed)
|
||||
sleep.assert_called_once_with(2)
|
||||
close_tab.assert_called_once_with("target-new", host="127.0.0.1:9222")
|
||||
|
||||
def test_apply_task_keeps_product_tab_after_failed_update(self):
|
||||
@@ -602,7 +625,9 @@ class EditorLoginTests(unittest.TestCase):
|
||||
), mock.patch(
|
||||
"app.editor.click_update",
|
||||
return_value={"clicked": False, "reason": "UPDATE_DISABLED"},
|
||||
), mock.patch("app.editor.close_tab") as close_tab:
|
||||
), mock.patch("app.editor.close_tab") as close_tab, mock.patch(
|
||||
"app.editor.time.sleep"
|
||||
) as sleep:
|
||||
result = editor.apply_task(
|
||||
{"debug_port": 9222},
|
||||
{"item_id": "51100639510", "new_title": "新标题"},
|
||||
@@ -613,6 +638,7 @@ class EditorLoginTests(unittest.TestCase):
|
||||
self.assertEqual("UPDATE_DISABLED", result["error"])
|
||||
self.assertTrue(cdp.closed)
|
||||
close_tab.assert_not_called()
|
||||
sleep.assert_not_called()
|
||||
|
||||
def test_apply_task_passes_old_cover_backup_to_replace_cover(self):
|
||||
cdp = FakeProductCDP("ws-new")
|
||||
@@ -640,8 +666,8 @@ class EditorLoginTests(unittest.TestCase):
|
||||
old_cover_path="old.jpg",
|
||||
)
|
||||
|
||||
def test_replace_cover_full_slots_requires_old_cover_backup(self):
|
||||
cdp = FakeCoverCDP(count=9)
|
||||
def test_replace_cover_requires_old_cover_backup_before_delete(self):
|
||||
cdp = FakeCoverCDP(count=8)
|
||||
missing_backup = os.path.join(os.path.dirname(__file__), "missing-old-cover.jpg")
|
||||
|
||||
with mock.patch("app.editor.time.sleep"):
|
||||
@@ -678,6 +704,61 @@ class EditorLoginTests(unittest.TestCase):
|
||||
self.assertEqual("https://susercontent.com/new-cover.jpg", result["new_src"])
|
||||
|
||||
|
||||
def test_replace_cover_deletes_first_for_non_full_image_slots(self):
|
||||
cdp = FakeCoverCDP(count=8)
|
||||
|
||||
with mock.patch("app.editor.time.sleep"):
|
||||
result = editor.replace_cover(
|
||||
cdp,
|
||||
__file__,
|
||||
old_cover_path=__file__,
|
||||
timeout=1,
|
||||
)
|
||||
|
||||
self.assertTrue(result["ok"])
|
||||
self.assertTrue(cdp.delete_clicked)
|
||||
self.assertTrue(cdp.confirm_clicked)
|
||||
self.assertTrue(cdp.uploaded)
|
||||
self.assertTrue(cdp.dragged)
|
||||
self.assertEqual(8, result["count_before"])
|
||||
self.assertEqual(8, result["count_after"])
|
||||
self.assertEqual(7, result["delete"]["count_after"])
|
||||
|
||||
def test_replace_cover_clicks_upload_tile_before_setting_files(self):
|
||||
cdp = FakeCoverCDP(count=9, require_upload_tile_click=True)
|
||||
|
||||
with mock.patch("app.editor.time.sleep"):
|
||||
result = editor.replace_cover(
|
||||
cdp,
|
||||
__file__,
|
||||
old_cover_path=__file__,
|
||||
timeout=1,
|
||||
)
|
||||
|
||||
self.assertTrue(result["ok"])
|
||||
self.assertTrue(cdp.upload_tile_clicked)
|
||||
self.assertTrue(cdp.uploaded)
|
||||
self.assertIn("DOM.setFileInputFiles", [method for method, _params in cdp.sent])
|
||||
self.assertTrue(cdp.upload_tile_clicked_before_file_set)
|
||||
self.assertEqual({"clicked": True, "reason": None}, result["upload_click"])
|
||||
|
||||
|
||||
def test_replace_cover_reports_upload_tile_not_ready(self):
|
||||
cdp = FakeCoverCDP(count=8, upload_tile_click=False)
|
||||
|
||||
with mock.patch("app.editor.time.sleep"):
|
||||
result = editor.replace_cover(
|
||||
cdp,
|
||||
__file__,
|
||||
old_cover_path=__file__,
|
||||
timeout=1,
|
||||
)
|
||||
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual("UPLOAD_TILE_NOT_READY", result["reason"])
|
||||
self.assertFalse(cdp.uploaded)
|
||||
self.assertEqual("NO_UPLOAD_TILE", result["upload_click"]["reason"])
|
||||
|
||||
def test_replace_cover_full_slots_deletes_before_upload_input_is_ready(self):
|
||||
cdp = FakeCoverCDP(
|
||||
count=9,
|
||||
@@ -742,7 +823,12 @@ class EditorLoginTests(unittest.TestCase):
|
||||
cdp = FakeCoverCDP(count=8, upload_mode="processing")
|
||||
|
||||
with mock.patch("app.editor.time.sleep"):
|
||||
result = editor.replace_cover(cdp, __file__, timeout=0.01)
|
||||
result = editor.replace_cover(
|
||||
cdp,
|
||||
__file__,
|
||||
old_cover_path=__file__,
|
||||
timeout=0.01,
|
||||
)
|
||||
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual("UPLOAD_STILL_PROCESSING", result["reason"])
|
||||
@@ -754,12 +840,33 @@ class EditorLoginTests(unittest.TestCase):
|
||||
cdp = FakeCoverCDP(count=8, upload_mode="error")
|
||||
|
||||
with mock.patch("app.editor.time.sleep"):
|
||||
result = editor.replace_cover(cdp, __file__, timeout=1)
|
||||
result = editor.replace_cover(
|
||||
cdp,
|
||||
__file__,
|
||||
old_cover_path=__file__,
|
||||
timeout=1,
|
||||
)
|
||||
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual("UPLOAD_PAGE_ERROR", result["reason"])
|
||||
self.assertIn("圖片格式錯誤", result["upload_state"]["errors"])
|
||||
|
||||
def test_replace_cover_reports_duplicate_upload_toast(self):
|
||||
cdp = FakeCoverCDP(count=8, upload_mode="duplicate")
|
||||
|
||||
with mock.patch("app.editor.time.sleep"):
|
||||
result = editor.replace_cover(
|
||||
cdp,
|
||||
__file__,
|
||||
old_cover_path=__file__,
|
||||
timeout=1,
|
||||
)
|
||||
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual("UPLOAD_DUPLICATE_IMAGE", result["reason"])
|
||||
self.assertIn("有1張重複的圖片", result["upload_state"]["error_toasts"])
|
||||
self.assertIn("新封面与现有商品图片重复", editor._cover_upload_error_message(result))
|
||||
|
||||
def test_apply_task_cover_failure_returns_readable_upload_error(self):
|
||||
cdp = FakeProductCDP("ws-new")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user