feat: 完成T-502满9张封面替换
实现 replace_cover 满9张时先校验 old_cover_path 本地备份,备份缺失返回 OLD_COVER_BACKUP_MISSING 且不删除线上图片。 备份存在时点击第一张删除按钮并处理可见确认框,删除后继续上传新封面并拖到第一位;apply_task 会把任务 old_cover_path 传入 replace_cover。 补充 editor 单测覆盖备份缺失阻断、删除确认后上传拖首位、删除按钮缺失和 old_cover_path 传递;同步需求、架构、编码规则、任务看板、API、routes、current-state 与 progress。 已在测试商品 29671243750 上验证 9图删除和满9张替换的不提交流程;未点击更新保存线上。
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
@@ -51,6 +52,83 @@ class FakeProductCDP:
|
||||
self.closed = True
|
||||
|
||||
|
||||
def cover_rects(count, prefix="old", start=0):
|
||||
return [
|
||||
{
|
||||
"i": index,
|
||||
"x": 100 + index * 10,
|
||||
"y": 100,
|
||||
"left": 60 + index * 10,
|
||||
"top": 60,
|
||||
"w": 80,
|
||||
"h": 80,
|
||||
"src": f"https://susercontent.com/{prefix}-{start + index}.jpg",
|
||||
}
|
||||
for index in range(count)
|
||||
]
|
||||
|
||||
|
||||
class FakeCoverCDP:
|
||||
def __init__(self, count=9, delete_click=True, confirm_click=True):
|
||||
self.initial = cover_rects(count)
|
||||
self.after_delete = cover_rects(max(0, count - 1), prefix="kept", start=1)
|
||||
self.new_rect = {
|
||||
"i": max(0, count - 1),
|
||||
"x": 300,
|
||||
"y": 100,
|
||||
"left": 260,
|
||||
"top": 60,
|
||||
"w": 80,
|
||||
"h": 80,
|
||||
"src": "https://susercontent.com/new-cover.jpg",
|
||||
}
|
||||
self.delete_click = delete_click
|
||||
self.confirm_click = confirm_click
|
||||
self.delete_clicked = False
|
||||
self.confirm_clicked = False
|
||||
self.uploaded = False
|
||||
self.dragged = False
|
||||
self.sent = []
|
||||
self.drag_calls = []
|
||||
|
||||
def _rects(self):
|
||||
if self.uploaded and self.dragged:
|
||||
return [self.new_rect] + self.after_delete
|
||||
if self.uploaded:
|
||||
return self.after_delete + [self.new_rect]
|
||||
if self.confirm_clicked:
|
||||
return self.after_delete
|
||||
return self.initial
|
||||
|
||||
def val(self, expr):
|
||||
if expr == editor.JS_RECTS:
|
||||
return json.dumps(self._rects())
|
||||
if expr == editor.JS_CLICK_FIRST_DELETE:
|
||||
if not self.delete_click:
|
||||
return json.dumps({"clicked": False, "reason": "NO_DELETE_BUTTON"})
|
||||
self.delete_clicked = True
|
||||
return json.dumps({"clicked": True, "reason": None})
|
||||
if expr == editor.JS_CLICK_DELETE_CONFIRM:
|
||||
if not self.confirm_click:
|
||||
return json.dumps({"clicked": False, "reason": "NO_CONFIRM_BUTTON"})
|
||||
self.confirm_clicked = True
|
||||
return json.dumps({"clicked": True, "reason": None, "text": "刪除"})
|
||||
return True
|
||||
|
||||
def object_id(self, expr):
|
||||
return "upload-input"
|
||||
|
||||
def send(self, method, params=None):
|
||||
self.sent.append((method, params or {}))
|
||||
if method == "DOM.setFileInputFiles":
|
||||
self.uploaded = True
|
||||
return {}
|
||||
|
||||
def drag(self, *args, **kwargs):
|
||||
self.dragged = True
|
||||
self.drag_calls.append((args, kwargs))
|
||||
|
||||
|
||||
def cookie(name, domain=".shopee.tw"):
|
||||
return {"name": name, "domain": domain}
|
||||
|
||||
@@ -293,6 +371,84 @@ class EditorLoginTests(unittest.TestCase):
|
||||
self.assertTrue(cdp.closed)
|
||||
close_tab.assert_not_called()
|
||||
|
||||
def test_apply_task_passes_old_cover_backup_to_replace_cover(self):
|
||||
cdp = FakeProductCDP("ws-new")
|
||||
|
||||
with mock.patch("app.editor.open_product", return_value=cdp), mock.patch(
|
||||
"app.editor.replace_cover",
|
||||
return_value={"ok": True, "reason": None},
|
||||
) as replace_cover, mock.patch(
|
||||
"app.editor.click_update",
|
||||
return_value={"clicked": True, "reason": None},
|
||||
):
|
||||
result = editor.apply_task(
|
||||
{"debug_port": 9222},
|
||||
{
|
||||
"item_id": "51100639510",
|
||||
"new_cover_path": "new.jpg",
|
||||
"old_cover_path": "old.jpg",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertTrue(result["committed"])
|
||||
replace_cover.assert_called_once_with(
|
||||
cdp,
|
||||
"new.jpg",
|
||||
old_cover_path="old.jpg",
|
||||
)
|
||||
|
||||
def test_replace_cover_full_slots_requires_old_cover_backup(self):
|
||||
cdp = FakeCoverCDP(count=9)
|
||||
missing_backup = os.path.join(os.path.dirname(__file__), "missing-old-cover.jpg")
|
||||
|
||||
with mock.patch("app.editor.time.sleep"):
|
||||
result = editor.replace_cover(
|
||||
cdp,
|
||||
__file__,
|
||||
old_cover_path=missing_backup,
|
||||
)
|
||||
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual("OLD_COVER_BACKUP_MISSING", result["reason"])
|
||||
self.assertFalse(cdp.delete_clicked)
|
||||
self.assertFalse(cdp.uploaded)
|
||||
|
||||
def test_replace_cover_full_slots_deletes_first_then_uploads_and_drags(self):
|
||||
cdp = FakeCoverCDP(count=9)
|
||||
|
||||
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(9, result["count_before"])
|
||||
self.assertEqual(9, result["count_after"])
|
||||
self.assertEqual(8, result["delete"]["count_after"])
|
||||
self.assertEqual("https://susercontent.com/new-cover.jpg", result["new_src"])
|
||||
|
||||
def test_replace_cover_full_slots_reports_missing_delete_button(self):
|
||||
cdp = FakeCoverCDP(count=9, delete_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("NO_DELETE_BUTTON", result["reason"])
|
||||
self.assertFalse(cdp.uploaded)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user