T-567 增加封面候选画廊

This commit is contained in:
chengma
2026-07-09 11:32:08 +08:00
parent 71e51d0608
commit a391d2db99
4 changed files with 573 additions and 42 deletions
+203 -2
View File
@@ -12,13 +12,13 @@ sys.path.insert(0, os.path.dirname(__file__))
from _helpers import TempDirMixin
from app import gui
from app import accounts, ai, appconfig, db, prompts, update_check
from app import accounts, ai, appconfig, db, image_paths, prompts, update_check
if gui.QT_IMPORT_ERROR is not None:
raise unittest.SkipTest("PySide6 未安装")
from PySide6.QtCore import QItemSelectionModel, QRect
from PySide6.QtGui import QTextCursor
from PySide6.QtGui import QImage, QTextCursor
from PySide6.QtWidgets import QApplication, QCheckBox, QLineEdit, QPlainTextEdit, QProgressBar, QTableView
from app.gui import (
@@ -39,6 +39,7 @@ from app.gui import (
TAB_TITLES,
WriteBackWorker,
)
from app.gui.tabs.generate import CoverGalleryDialog, OriginalImageDialog
from app.gui.main_window import _fit_and_center_window
@@ -81,6 +82,13 @@ class GuiTests(TempDirMixin, unittest.TestCase):
}
return cfg
def write_test_image(self, path, width=32, height=32):
os.makedirs(os.path.dirname(path), exist_ok=True)
image = QImage(width, height, QImage.Format_RGB32)
image.fill(0xFF336699)
self.assertTrue(image.save(path))
return path
def assert_foreground(self, model, row, column, color):
value = model.data(model.index(row, column), gui.Qt.ForegroundRole)
self.assertIsNotNone(value)
@@ -2068,6 +2076,199 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_cover_gallery_lists_candidates_and_selects_current_cover(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
account = accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
db.init_db(cfg["db_path"])
batch_id = db.create_batch(["input.xlsx"], path=cfg["db_path"])
db.insert_tasks(
batch_id,
[
{
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
"source_sheet": "商品",
"source_row": 2,
"account_name": "Excel主店",
"alias": "alias-a",
"item_id": "51100639510",
}
],
path=cfg["db_path"],
)
task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
old_cover = self.write_test_image(os.path.join(temp_dir, "old.jpg"))
canonical = image_paths.task_image_path(cfg["image_dir"], task, account, "new")
prefix = os.path.splitext(canonical)[0]
archive_newer = self.write_test_image(f"{prefix}_20260709101000.jpg")
archive_older = self.write_test_image(f"{prefix}_20260709094800.jpg")
old_candidate = f"{prefix.replace('_new', '_old')}.jpg"
self.write_test_image(canonical)
self.write_test_image(old_candidate)
db.set_collected(task.id, "旧标题", old_cover, path=cfg["db_path"])
db.set_generated(task.id, "新标题", canonical, path=cfg["db_path"])
task = db.get_task(task.id, path=cfg["db_path"])
dialog = CoverGalleryDialog(task, cfg["image_dir"], cfg["db_path"], account=account)
self.addCleanup(dialog.close)
self.assertEqual([canonical, archive_newer, archive_older], dialog.candidates)
self.assertTrue(dialog.candidate_buttons[canonical].isChecked())
self.assertEqual(canonical, dialog.selected_path)
self.assertNotIn(old_candidate, dialog.candidates)
renamed_archive = archive_newer.replace(".jpg", "_renamed.jpg")
os.rename(archive_newer, renamed_archive)
self.assertTrue(os.path.exists(renamed_archive))
self.assert_removed(temp_dir)
def test_cover_gallery_save_switches_current_cover(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
account = accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
db.init_db(cfg["db_path"])
batch_id = db.create_batch(["input.xlsx"], path=cfg["db_path"])
db.insert_tasks(
batch_id,
[
{
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
"source_sheet": "商品",
"source_row": 2,
"account_name": "Excel主店",
"alias": "alias-a",
"item_id": "51100639510",
}
],
path=cfg["db_path"],
)
task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
canonical = image_paths.task_image_path(cfg["image_dir"], task, account, "new")
selected = self.write_test_image(os.path.splitext(canonical)[0] + "_20260709101000.jpg")
self.write_test_image(canonical)
db.set_generated(task.id, "新标题", canonical, path=cfg["db_path"])
task = db.get_task(task.id, path=cfg["db_path"])
dialog = CoverGalleryDialog(task, cfg["image_dir"], cfg["db_path"], account=account)
self.addCleanup(dialog.close)
dialog.candidate_buttons[selected].setChecked(True)
self.assertTrue(dialog.save_selection())
updated = db.get_task(task.id, path=cfg["db_path"])
self.assertEqual(os.path.abspath(selected), updated.new_cover_path)
self.assertEqual("generated", updated.stage)
self.assertEqual("pending", updated.status)
self.assertTrue(dialog.changed)
self.assert_removed(temp_dir)
def test_cover_gallery_committed_cancel_does_not_write_db(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
account = accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
db.init_db(cfg["db_path"])
batch_id = db.create_batch(["input.xlsx"], path=cfg["db_path"])
db.insert_tasks(
batch_id,
[
{
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
"source_sheet": "商品",
"source_row": 2,
"account_name": "Excel主店",
"alias": "alias-a",
"item_id": "51100639510",
}
],
path=cfg["db_path"],
)
task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
canonical = image_paths.task_image_path(cfg["image_dir"], task, account, "new")
selected = self.write_test_image(os.path.splitext(canonical)[0] + "_20260709101000.jpg")
self.write_test_image(canonical)
db.set_generated(task.id, "新标题", canonical, path=cfg["db_path"])
db.set_applied(task.id, True, path=cfg["db_path"])
task = db.get_task(task.id, path=cfg["db_path"])
dialog = CoverGalleryDialog(task, cfg["image_dir"], cfg["db_path"], account=account)
self.addCleanup(dialog.close)
dialog.candidate_buttons[selected].setChecked(True)
message_box, boxes = self.make_fake_message_box("取消")
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
self.assertFalse(dialog.save_selection())
updated = db.get_task(task.id, path=cfg["db_path"])
self.assertEqual(canonical, updated.new_cover_path)
self.assertEqual("applied", updated.stage)
self.assertEqual(1, updated.committed)
self.assertIn("重复更新会再次提交线上", boxes[0].text)
self.assertFalse(dialog.changed)
self.assert_removed(temp_dir)
def test_cover_gallery_handles_missing_current_and_empty_candidates(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
account = accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
db.init_db(cfg["db_path"])
batch_id = db.create_batch(["input.xlsx"], path=cfg["db_path"])
db.insert_tasks(
batch_id,
[
{
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
"source_sheet": "商品",
"source_row": 2,
"account_name": "Excel主店",
"alias": "alias-a",
"item_id": "51100639510",
},
{
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
"source_sheet": "商品",
"source_row": 3,
"account_name": "Excel主店",
"alias": "alias-a",
"item_id": "51100639511",
},
],
path=cfg["db_path"],
)
first, second = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])
missing_current = os.path.join(temp_dir, "missing.jpg")
db.set_generated(first.id, "新标题A", missing_current, path=cfg["db_path"])
first_candidate = image_paths.task_image_path(cfg["image_dir"], first, account, "new")
self.write_test_image(first_candidate)
first = db.get_task(first.id, path=cfg["db_path"])
missing_dialog = CoverGalleryDialog(first, cfg["image_dir"], cfg["db_path"], account=account)
self.addCleanup(missing_dialog.close)
self.assertIsNone(missing_dialog.selected_path)
self.assertFalse(missing_dialog.save_button.isEnabled())
self.assertIn("当前生效封面文件不存在", missing_dialog.status_label.text())
db.set_generated(second.id, "新标题B", os.path.join(temp_dir, "none.jpg"), path=cfg["db_path"])
second = db.get_task(second.id, path=cfg["db_path"])
empty_dialog = CoverGalleryDialog(second, cfg["image_dir"], cfg["db_path"], account=account)
self.addCleanup(empty_dialog.close)
self.assertEqual([], empty_dialog.candidates)
self.assertFalse(empty_dialog.save_button.isEnabled())
self.assertIn("暂无生成封面图片", empty_dialog.status_label.text())
self.assert_removed(temp_dir)
def test_original_image_dialog_title_contains_resolution(self):
with self.make_temp_dir() as temp_dir:
image_path = self.write_test_image(os.path.join(temp_dir, "cover.jpg"), width=64, height=32)
dialog = OriginalImageDialog(image_path)
self.addCleanup(dialog.close)
self.assertIn("cover.jpg", dialog.windowTitle())
self.assertIn("64x32", dialog.windowTitle())
self.assert_removed(temp_dir)
def test_generate_tab_allows_editing_generated_title_locally(self):
with self.make_temp_dir() as temp_dir: