fix: clarify AI generation failure retry states
This commit is contained in:
@@ -222,6 +222,28 @@ def _notify_retry(callback, step, attempt, attempts, exc, model):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _task_attempt_count(task, field):
|
||||||
|
try:
|
||||||
|
return int(getattr(task, field, 0) or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def is_generatable_task(task):
|
||||||
|
"""判断任务是否能由② AI生成执行或重试。"""
|
||||||
|
|
||||||
|
stage = str(getattr(task, "stage", "") or "")
|
||||||
|
status = str(getattr(task, "status", "") or "")
|
||||||
|
if status in {"running", "skipped"}:
|
||||||
|
return False
|
||||||
|
if stage == "collected":
|
||||||
|
return True
|
||||||
|
if stage == "generated" and status == "failed":
|
||||||
|
return _task_attempt_count(task, "apply_attempts") == 0
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=None):
|
def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=None):
|
||||||
"""Generate titles first, then covers, and persist each successful task."""
|
"""Generate titles first, then covers, and persist each successful task."""
|
||||||
|
|
||||||
@@ -256,7 +278,7 @@ def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=No
|
|||||||
should_stop = should_stop or (lambda: False)
|
should_stop = should_stop or (lambda: False)
|
||||||
eligible = [
|
eligible = [
|
||||||
task for task in list(tasks)
|
task for task in list(tasks)
|
||||||
if getattr(task, "stage", None) == "collected"
|
if is_generatable_task(task)
|
||||||
]
|
]
|
||||||
summary = {
|
summary = {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
|
|||||||
@@ -275,10 +275,30 @@ class GenerateTaskTableModel(QAbstractTableModel):
|
|||||||
return task.account_name or task.alias or ""
|
return task.account_name or task.alias or ""
|
||||||
|
|
||||||
def _status_text(self, task):
|
def _status_text(self, task):
|
||||||
|
if task.status == "failed":
|
||||||
|
return self._failed_status_text(task)
|
||||||
if task.status in self.STATUS_TEXT and task.status != "pending":
|
if task.status in self.STATUS_TEXT and task.status != "pending":
|
||||||
return self.STATUS_TEXT[task.status]
|
return self.STATUS_TEXT[task.status]
|
||||||
return self.STAGE_TEXT.get(task.stage, task.stage)
|
return self.STAGE_TEXT.get(task.stage, task.stage)
|
||||||
|
|
||||||
|
def _failed_status_text(self, task):
|
||||||
|
collect_attempts = self._attempt_count(task, "collect_attempts")
|
||||||
|
generate_attempts = self._attempt_count(task, "generate_attempts")
|
||||||
|
apply_attempts = self._attempt_count(task, "apply_attempts")
|
||||||
|
if apply_attempts > 0:
|
||||||
|
return "更新失败"
|
||||||
|
if generate_attempts > 0:
|
||||||
|
return "生成失败"
|
||||||
|
if collect_attempts > 0 or getattr(task, "stage", None) == "imported":
|
||||||
|
return "采集失败"
|
||||||
|
return "失败"
|
||||||
|
|
||||||
|
def _attempt_count(self, task, field):
|
||||||
|
try:
|
||||||
|
return int(getattr(task, field, 0) or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return 0
|
||||||
|
|
||||||
def _status_color(self, task):
|
def _status_color(self, task):
|
||||||
base_color = _status_base_color(getattr(task, "status", None))
|
base_color = _status_base_color(getattr(task, "status", None))
|
||||||
if base_color is not None:
|
if base_color is not None:
|
||||||
|
|||||||
@@ -470,10 +470,10 @@ class GenerateTab(QWidget):
|
|||||||
generate_cover = bool(self.generate_cover_checkbox.isChecked())
|
generate_cover = bool(self.generate_cover_checkbox.isChecked())
|
||||||
tasks = [
|
tasks = [
|
||||||
task for task in self.model.tasks
|
task for task in self.model.tasks
|
||||||
if getattr(task, "stage", None) == "collected"
|
if ai.is_generatable_task(task)
|
||||||
]
|
]
|
||||||
if not tasks:
|
if not tasks:
|
||||||
self._set_status("当前筛选结果没有可生成任务")
|
self._set_status("当前筛选结果没有待生成或生成失败可重试任务;请先在①导入采集完成旧数据采集")
|
||||||
return
|
return
|
||||||
prompt_values = {
|
prompt_values = {
|
||||||
"title": self.title_prompt_edit.toPlainText(),
|
"title": self.title_prompt_edit.toPlainText(),
|
||||||
@@ -896,7 +896,7 @@ class GenerateTab(QWidget):
|
|||||||
if selected_status in (None, "all"):
|
if selected_status in (None, "all"):
|
||||||
return True
|
return True
|
||||||
if selected_status == "to_generate":
|
if selected_status == "to_generate":
|
||||||
return task.stage == "collected" and task.status in {"success", "pending"}
|
return ai.is_generatable_task(task) and task.status != "failed"
|
||||||
if selected_status == "generated":
|
if selected_status == "generated":
|
||||||
return task.stage == "generated"
|
return task.stage == "generated"
|
||||||
if selected_status == "applied":
|
if selected_status == "applied":
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from .widgets import *
|
from .widgets import *
|
||||||
class GenerateWorker(BaseWorker):
|
class GenerateWorker(BaseWorker):
|
||||||
"""Generate titles and covers for collected tasks."""
|
"""Generate titles and covers for eligible collected or failed generation tasks."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -39,7 +39,7 @@ class GenerateWorker(BaseWorker):
|
|||||||
self._account_by_alias = account_by_alias
|
self._account_by_alias = account_by_alias
|
||||||
eligible = [
|
eligible = [
|
||||||
task for task in self.tasks
|
task for task in self.tasks
|
||||||
if getattr(task, "stage", None) == "collected"
|
if ai.is_generatable_task(task)
|
||||||
]
|
]
|
||||||
self._eligible_total = len(eligible)
|
self._eligible_total = len(eligible)
|
||||||
self._task_positions = {
|
self._task_positions = {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
- T-517 已完成:⑤设置页将「Shopee 更新安全 / 执行模式」前置为独立分区,将「基础设施(路径与端口)」后置;`test_item_id`、`dry_run` 不再有用户可操作控件,保存设置仍保留 `test_item_id` 兼容字段并固定 `dry_run=false`,③「检查本轮更新」语义不变。
|
- T-517 已完成:⑤设置页将「Shopee 更新安全 / 执行模式」前置为独立分区,将「基础设施(路径与端口)」后置;`test_item_id`、`dry_run` 不再有用户可操作控件,保存设置仍保留 `test_item_id` 兼容字段并固定 `dry_run=false`,③「检查本轮更新」语义不变。
|
||||||
- T-518 已完成:②AI生成左栏保留模板选择、编辑内容、新建/保存等高频入口,把封面模板「另存为 / 重命名 / 删除」收敛进②本页「模板操作」菜单;动作仍调用原有保存/重命名/删除方法,删除二次确认、错误处理和 prompts 文件结构不变,不涉及 AI/Shopee/CDP。
|
- T-518 已完成:②AI生成左栏保留模板选择、编辑内容、新建/保存等高频入口,把封面模板「另存为 / 重命名 / 删除」收敛进②本页「模板操作」菜单;动作仍调用原有保存/重命名/删除方法,删除二次确认、错误处理和 prompts 文件结构不变,不涉及 AI/Shopee/CDP。
|
||||||
- T-519 已完成:②AI生成底部增加标题/图片两条独立进度条,现有「AI生成运行日志」改为用户可读、自动滚动、脱敏的长任务日志;日志显示本轮开始、标题/图片开始与成功、商品ID/店铺、调用失败重试、失败、停止请求和完成汇总,不显示 API Key、密码、Cookie、token、完整请求体、base64 图片或超长 prompt,不改变 AI/DB/Excel/Shopee/CDP 核心流程。
|
- T-519 已完成:②AI生成底部增加标题/图片两条独立进度条,现有「AI生成运行日志」改为用户可读、自动滚动、脱敏的长任务日志;日志显示本轮开始、标题/图片开始与成功、商品ID/店铺、调用失败重试、失败、停止请求和完成汇总,不显示 API Key、密码、Cookie、token、完整请求体、base64 图片或超长 prompt,不改变 AI/DB/Excel/Shopee/CDP 核心流程。
|
||||||
- T-520 已完成:②AI生成页增加「生成封面图片(成本较高)」开关,默认关闭以避免无意产生图片模型成本;关闭时标题生成成功即可写库进入 generated,`new_cover_path=NULL`,③只更新标题且不受「允许更新封面」阻断;开启时保持标题后图片两段生成流程。未改 DB schema、AI HTTP 协议、Excel、Shopee/CDP 更新流程。
|
- T-520 已完成:②AI生成页增加「生成封面图片(成本较高)」开关,默认关闭以避免无意产生图片模型成本;关闭时标题生成成功即可写库进入 generated,`new_cover_path=NULL`,③只更新标题且不受「允许更新封面」阻断;开启时保持标题后图片两段生成流程。2026-07-06 运行修正:②AI生成页会把失败按阶段显示为“采集失败 / 生成失败 / 更新失败”;「开始生成」只处理已采集待生成或生成失败可重试任务,采集失败会提示先回到①完成旧数据采集,更新失败不会被②误重试。未改 DB schema、AI HTTP 协议、Excel、Shopee/CDP 更新流程。
|
||||||
- T-528 已完成:②AI生成页在 cmhub backend 下显示生成后的剩余点数;GenerateWorker 读取计费 metadata,把 points_cost、points_balance、call_id 写入脱敏运行日志;遇到 CMHubError.code=insufficient_points 时弹出「点数不足,请先充值」并中止本轮未开始任务,不靠中文错误字符串匹配。未改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程。
|
- T-528 已完成:②AI生成页在 cmhub backend 下显示生成后的剩余点数;GenerateWorker 读取计费 metadata,把 points_cost、points_balance、call_id 写入脱敏运行日志;遇到 CMHubError.code=insufficient_points 时弹出「点数不足,请先充值」并中止本轮未开始任务,不靠中文错误字符串匹配。未改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程。
|
||||||
- T-529 已完成:普通默认 `ai.backend=cmhub`;⑤设置页不再显示「AI 后端」label/dropdown、direct 模型选择、模型详情或标题/图片模型角色下拉,直接展示 cmhub 网关配置;保存设置固定写 `ai.backend=cmhub` 且允许先保存不完整 cmhub 配置,②生成时仍由 `app/ai.py` 提示补齐 Base URL/API Key/别名。direct 代码和 `config/ai_models.json` 保留为内部兼容/手工回滚,不改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程。
|
- T-529 已完成:普通默认 `ai.backend=cmhub`;⑤设置页不再显示「AI 后端」label/dropdown、direct 模型选择、模型详情或标题/图片模型角色下拉,直接展示 cmhub 网关配置;保存设置固定写 `ai.backend=cmhub` 且允许先保存不完整 cmhub 配置,②生成时仍由 `app/ai.py` 提示补齐 Base URL/API Key/别名。direct 代码和 `config/ai_models.json` 保留为内部兼容/手工回滚,不改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程。
|
||||||
- T-530 已完成:`appconfig.normalize_cmhub_base_url()` / `cmhub_request_url()` 会把 cmhub Base URL 规整到 scheme+host(+port),去掉 `/api`、`/api/v1`、其它路径、查询串和片段;⑤设置页输入框提示只填网关根,保存/刷新前同步规整;cmhub HTTP 404 统一映射为 `CMHubError(code="not_found")`,显示“cmhub 接口不存在,请检查 Base URL 或该实例是否已部署 /api/v1/models”。未改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程。
|
- T-530 已完成:`appconfig.normalize_cmhub_base_url()` / `cmhub_request_url()` 会把 cmhub Base URL 规整到 scheme+host(+port),去掉 `/api`、`/api/v1`、其它路径、查询串和片段;⑤设置页输入框提示只填网关根,保存/刷新前同步规整;cmhub HTTP 404 统一映射为 `CMHubError(code="not_found")`,显示“cmhub 接口不存在,请检查 Base URL 或该实例是否已部署 /api/v1/models”。未改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程。
|
||||||
|
|||||||
@@ -164,6 +164,51 @@ curl -i "https://<你的BaseURL>/api/v1/balance" -H "Authorization: Bearer sk_cm
|
|||||||
|
|
||||||
T-530 已实现:保存和请求前都会把 Base URL 规整为网关根,去掉多余路径、查询串和片段;HTTP 404 会映射为 `not_found`,显示“cmhub 接口不存在,请检查 Base URL 或该实例是否已部署 /api/v1/models”,不再透传 `notfound` 这类生涩原文。
|
T-530 已实现:保存和请求前都会把 Base URL 规整为网关根,去掉多余路径、查询串和片段;HTTP 404 会映射为 `not_found`,显示“cmhub 接口不存在,请检查 Base URL 或该实例是否已部署 /api/v1/models”,不再透传 `notfound` 这类生涩原文。
|
||||||
|
|
||||||
|
## ②AI生成提示“当前筛选结果没有可生成任务”
|
||||||
|
|
||||||
|
### 现象
|
||||||
|
|
||||||
|
在②AI生成模块选中某个批次,列表里看到多条“失败”记录,点击「开始生成」后,状态栏提示:
|
||||||
|
|
||||||
|
```text
|
||||||
|
当前筛选结果没有待生成或生成失败可重试任务;请先在①导入采集完成旧数据采集
|
||||||
|
```
|
||||||
|
|
||||||
|
旧版本可能提示:
|
||||||
|
|
||||||
|
```text
|
||||||
|
当前筛选结果没有可生成任务
|
||||||
|
```
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
②AI生成只能处理两类任务:
|
||||||
|
|
||||||
|
- 已完成①采集的待生成任务:底层通常是 `stage=collected`。
|
||||||
|
- AI 生成阶段失败、可以重试的任务:例如生成标题或生成封面失败。
|
||||||
|
|
||||||
|
如果列表里的“失败”其实是①采集失败,任务还停在 `stage=imported/status=failed`,没有旧标题和旧封面,②无法直接生成。2026-07-06 的实测问题就是这种情况:本地最新批次 5 条任务都是 `stage=imported/status=failed/collect_attempts>0/generate_attempts=0`,属于采集失败,不是 AI 生成失败。
|
||||||
|
|
||||||
|
如果失败来自③更新shopee,任务通常已有新标题/新封面,但这是更新阶段失败,也不应回到②自动重新生成。
|
||||||
|
|
||||||
|
### 当前处理
|
||||||
|
|
||||||
|
当前代码已把②AI生成列表的失败状态细分为:
|
||||||
|
|
||||||
|
- `采集失败`:需要先回①重新采集旧标题/旧封面。
|
||||||
|
- `生成失败`:可以在②当前筛选范围内再次点击「开始生成」重试。
|
||||||
|
- `更新失败`:应去③更新shopee重试或重置更新状态,不会被②误当成生成任务。
|
||||||
|
|
||||||
|
②「开始生成」使用统一的 `ai.is_generatable_task()` 判断可生成范围,GUI、Worker 和底层 `generate_batch()` 口径一致。
|
||||||
|
|
||||||
|
### 推荐处理
|
||||||
|
|
||||||
|
1. 如果②状态列显示“采集失败”:回到①导入采集,确认对应账号 Chrome 已启动且已登录,再重新采集旧标题/旧封面。
|
||||||
|
2. 如果②状态列显示“生成失败”:留在②,确认 cmhub Base URL、API Key、别名和点数正常后,直接点击「开始生成」重试当前筛选结果。
|
||||||
|
3. 如果②状态列显示“更新失败”:到③更新shopee处理;需要重复更新时使用③的重置更新状态入口,不要在②重新生成。
|
||||||
|
|
||||||
|
排查时不要把 `cmshopee.db`、`config/cmhub.json`、`config/ai_models.json` 或任何 API Key、密码、Cookie 发到聊天、文档或提交里。
|
||||||
|
|
||||||
## AI生成:标题成功但图片生成失败,且看不到原因
|
## AI生成:标题成功但图片生成失败,且看不到原因
|
||||||
|
|
||||||
当前代码会为 ② AI生成写两层日志:
|
当前代码会为 ② AI生成写两层日志:
|
||||||
|
|||||||
@@ -1176,3 +1176,9 @@
|
|||||||
- 文档:同步 `AGENTS.md`、`docs/05-coding-rules.md`、`docs/current-state.md`。
|
- 文档:同步 `AGENTS.md`、`docs/05-coding-rules.md`、`docs/current-state.md`。
|
||||||
- 代码:同步修正 T-531 设置未保存离开确认框,改用自定义中文按钮“保存 / 放弃 / 取消”,避免系统默认按钮显示英文。
|
- 代码:同步修正 T-531 设置未保存离开确认框,改用自定义中文按钮“保存 / 放弃 / 取消”,避免系统默认按钮显示英文。
|
||||||
- 验证:`python -m py_compile app\gui\main_window.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p test_gui.py` 通过(86 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过(195 tests);`git diff --check` 通过,仅有本机 LF/CRLF 提示。
|
- 验证:`python -m py_compile app\gui\main_window.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p test_gui.py` 通过(86 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过(195 tests);`git diff --check` 通过,仅有本机 LF/CRLF 提示。
|
||||||
|
## 【2026-07-06】运行修正 · ②AI生成失败状态分阶段提示
|
||||||
|
|
||||||
|
- 现象:在②AI生成选择一个批次,列表都是“失败”状态时点击「开始生成」,状态栏提示“当前筛选结果没有可生成任务”。
|
||||||
|
- 排查:本地最新未删除批次 `20260702_171000_75bd2557` 的 5 条任务均为 `stage=imported/status=failed/collect_attempts=3/generate_attempts=0/apply_attempts=0`,属于①采集失败,不是 AI 生成失败;②没有旧标题/旧封面,不能直接生成。
|
||||||
|
- 修正:新增 `ai.is_generatable_task()` 统一 GUI/Worker/底层批处理口径;②状态列把失败细分显示为“采集失败 / 生成失败 / 更新失败”;「开始生成」无可重试任务时提示先到①完成旧数据采集。AI 生成失败可重试,③更新失败不会被②误重试。文档补充 `docs/troubleshooting.md` 的同名排查条目。
|
||||||
|
- 测试:新增 `test_generate_batch_retries_failed_generation_record_after_existing_result`、`test_generate_batch_does_not_retry_apply_failed_records`、`test_generate_tab_explains_collect_failed_records_are_not_generatable`。
|
||||||
|
|||||||
@@ -757,6 +757,66 @@ class AITests(TempDirMixin, unittest.TestCase):
|
|||||||
|
|
||||||
self.assert_removed(temp_dir)
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
def test_generate_batch_retries_failed_generation_record_after_existing_result(self):
|
||||||
|
with self.make_temp_dir() as temp_dir:
|
||||||
|
cfg = self._config()
|
||||||
|
cfg["db_path"] = os.path.join(temp_dir, "cmshopee.db")
|
||||||
|
cfg["image_dir"] = os.path.join(temp_dir, "images")
|
||||||
|
cfg["ai"]["generate_cover"] = False
|
||||||
|
batch_id, tasks = self._collected_tasks(temp_dir, cfg, ["旧标题"])
|
||||||
|
db.set_generated(tasks[0].id, "旧AI标题", "old-new.jpg", path=cfg["db_path"])
|
||||||
|
db.mark_failed(tasks[0].id, "generate", "上次生成失败", path=cfg["db_path"])
|
||||||
|
retry_task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
|
||||||
|
|
||||||
|
with mock.patch("app.ai.gen_title", return_value="重新生成标题") as gen_title, \
|
||||||
|
mock.patch("app.ai.gen_cover") as gen_cover:
|
||||||
|
summary = ai.generate_batch(
|
||||||
|
[retry_task],
|
||||||
|
{"title": "标题提示", "cover": "封面"},
|
||||||
|
ai_cfg={"config": cfg, "db_path": cfg["db_path"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(summary["ok"])
|
||||||
|
self.assertEqual(1, summary["total"])
|
||||||
|
self.assertEqual(1, summary["generated_done"])
|
||||||
|
gen_title.assert_called_once()
|
||||||
|
gen_cover.assert_not_called()
|
||||||
|
updated = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
|
||||||
|
self.assertEqual("generated", updated.stage)
|
||||||
|
self.assertEqual("success", updated.status)
|
||||||
|
self.assertEqual("重新生成标题", updated.new_title)
|
||||||
|
self.assertIsNone(updated.new_cover_path)
|
||||||
|
|
||||||
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
def test_generate_batch_does_not_retry_apply_failed_records(self):
|
||||||
|
with self.make_temp_dir() as temp_dir:
|
||||||
|
cfg = self._config()
|
||||||
|
cfg["db_path"] = os.path.join(temp_dir, "cmshopee.db")
|
||||||
|
cfg["image_dir"] = os.path.join(temp_dir, "images")
|
||||||
|
batch_id, tasks = self._collected_tasks(temp_dir, cfg, ["旧标题"])
|
||||||
|
db.set_generated(tasks[0].id, "新标题", "new.jpg", path=cfg["db_path"])
|
||||||
|
db.mark_failed(tasks[0].id, "apply", "更新失败", path=cfg["db_path"])
|
||||||
|
update_failed_task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
|
||||||
|
|
||||||
|
with mock.patch("app.ai.gen_title") as gen_title, \
|
||||||
|
mock.patch("app.ai.gen_cover") as gen_cover:
|
||||||
|
summary = ai.generate_batch(
|
||||||
|
[update_failed_task],
|
||||||
|
{"title": "标题提示", "cover": "封面"},
|
||||||
|
ai_cfg={"config": cfg, "db_path": cfg["db_path"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(summary["ok"])
|
||||||
|
self.assertEqual(0, summary["total"])
|
||||||
|
gen_title.assert_not_called()
|
||||||
|
gen_cover.assert_not_called()
|
||||||
|
unchanged = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
|
||||||
|
self.assertEqual("generated", unchanged.stage)
|
||||||
|
self.assertEqual("failed", unchanged.status)
|
||||||
|
self.assertEqual(1, unchanged.apply_attempts)
|
||||||
|
|
||||||
|
self.assert_removed(temp_dir)
|
||||||
def test_generate_batch_stop_before_scheduling_keeps_tasks_collected(self):
|
def test_generate_batch_stop_before_scheduling_keeps_tasks_collected(self):
|
||||||
with self.make_temp_dir() as temp_dir:
|
with self.make_temp_dir() as temp_dir:
|
||||||
cfg = self._config()
|
cfg = self._config()
|
||||||
|
|||||||
+34
-1
@@ -1934,6 +1934,39 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
|
|
||||||
self.assert_removed(temp_dir)
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
def test_generate_tab_explains_collect_failed_records_are_not_generatable(self):
|
||||||
|
with self.make_temp_dir() as temp_dir:
|
||||||
|
cfg = self.make_config(temp_dir)
|
||||||
|
accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
|
||||||
|
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]
|
||||||
|
db.mark_failed(task.id, "collect", "采集失败", path=cfg["db_path"])
|
||||||
|
statuses = []
|
||||||
|
tab = GenerateTab(config=cfg, status_callback=statuses.append)
|
||||||
|
self.addCleanup(tab.close)
|
||||||
|
|
||||||
|
self.assertEqual("采集失败", tab.model.index(0, 4).data())
|
||||||
|
tab.start_generate()
|
||||||
|
|
||||||
|
self.assertIsNone(tab.generate_worker)
|
||||||
|
self.assertIn("没有待生成或生成失败可重试任务", statuses[-1])
|
||||||
|
self.assertIn("①导入采集", statuses[-1])
|
||||||
|
|
||||||
|
self.assert_removed(temp_dir)
|
||||||
def test_apply_tab_does_not_auto_mix_latest_apply_run_log(self):
|
def test_apply_tab_does_not_auto_mix_latest_apply_run_log(self):
|
||||||
with self.make_temp_dir() as temp_dir:
|
with self.make_temp_dir() as temp_dir:
|
||||||
cfg = self.make_config(temp_dir)
|
cfg = self.make_config(temp_dir)
|
||||||
@@ -3028,7 +3061,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
tab.status_filter.setCurrentIndex(tab.status_filter.findData("all"))
|
tab.status_filter.setCurrentIndex(tab.status_filter.findData("all"))
|
||||||
tab.batch_filter.setCurrentIndex(tab.batch_filter.findData(batch_b))
|
tab.batch_filter.setCurrentIndex(tab.batch_filter.findData(batch_b))
|
||||||
self.assertEqual(1, tab.model.rowCount())
|
self.assertEqual(1, tab.model.rowCount())
|
||||||
self.assertEqual("失败", tab.model.index(0, 4).data())
|
self.assertEqual("生成失败", tab.model.index(0, 4).data())
|
||||||
self.assert_foreground(tab.model, 0, 4, gui.COLOR_DANGER)
|
self.assert_foreground(tab.model, 0, 4, gui.COLOR_DANGER)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"生成失败",
|
"生成失败",
|
||||||
|
|||||||
Reference in New Issue
Block a user