fix(worker): P1 修复任务状态被过期对象覆盖回pending + 封面metadata属性名错误
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 46s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m21s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 46s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 48s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m39s
AI Code Review / AI Code Review (pull_request) Successful in 1m40s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m41s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m20s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 572h28m49s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 572h29m35s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 572h29m36s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 572h29m38s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 572h29m39s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 572h28m50s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 572h28m51s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 572h29m39s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 573h3m25s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 573h3m27s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 46s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m21s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 46s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 48s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m39s
AI Code Review / AI Code Review (pull_request) Successful in 1m40s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m41s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m20s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 572h28m49s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 572h29m35s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 572h29m36s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 572h29m38s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 572h29m39s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 572h28m50s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 572h28m51s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 572h29m39s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 573h3m25s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 573h3m27s
Bug1: asset_analyses 持久化时使用任务开头加载的 gen_task 内存对象 (status=pending) 整体 _repo.update(),把已被 _update_task_status 改为 running 的 DB status 覆盖回 pending,导致 mark_completed 报非法状态转换。 改为独立 session 查询最新模型,只更新 extra_meta 字段。 Bug2: 封面候选帧持久化使用 _cover_model.metadata(SQLAlchemy 声明基类 保留的 MetaData 对象),赋值无效;ORM 属性名应为 extra_meta(DB列名 为 metadata)。两处全部修正。 新增 5 个防回归单测覆盖两个 Bug。
This commit is contained in:
@@ -1493,9 +1493,9 @@ def generate_video(self, task_id: str) -> dict:
|
||||
)
|
||||
if _cover_model:
|
||||
_cover_model.cover_url = cover_frame_url
|
||||
meta = dict(_cover_model.metadata or {})
|
||||
meta = dict(_cover_model.extra_meta or {})
|
||||
meta["cover_candidates"] = cover_candidates
|
||||
_cover_model.metadata = meta
|
||||
_cover_model.extra_meta = meta
|
||||
_cover_session.commit()
|
||||
finally:
|
||||
_cover_session.close()
|
||||
@@ -1680,10 +1680,24 @@ def generate_video(self, task_id: str) -> dict:
|
||||
logger.info("[task_id=%s] MediaKit 视频理解完成: %d 个素材", task_id, len(asset_urls))
|
||||
|
||||
# 保存分析结果到 extra_meta
|
||||
if asset_analyses and gen_task:
|
||||
gen_task.extra_meta = {**(gen_task.extra_meta or {}), "asset_analyses": asset_analyses}
|
||||
_repo.update(gen_task)
|
||||
_flush_logs(task_id, gen_task)
|
||||
if asset_analyses:
|
||||
_meta_session = SessionLocal()
|
||||
try:
|
||||
from packages.adapters.sqlalchemy_impl.models import (
|
||||
GenerationTaskModel,
|
||||
)
|
||||
_m = _meta_session.query(GenerationTaskModel).filter(
|
||||
GenerationTaskModel.id == task_id
|
||||
).first()
|
||||
if _m:
|
||||
existing = dict(_m.extra_meta or {})
|
||||
existing["asset_analyses"] = asset_analyses
|
||||
_m.extra_meta = existing
|
||||
_meta_session.commit()
|
||||
finally:
|
||||
_meta_session.close()
|
||||
if gen_task:
|
||||
_flush_logs(task_id, gen_task)
|
||||
except Exception:
|
||||
logger.warning("[task_id=%s] MediaKit 视频理解失败,继续渲染", task_id, exc_info=True)
|
||||
|
||||
@@ -1777,10 +1791,10 @@ def generate_video(self, task_id: str) -> dict:
|
||||
)
|
||||
if _cover_model:
|
||||
_cover_model.cover_url = cover_frame_url
|
||||
# 持久化完整候选列表到 metadata
|
||||
meta = dict(_cover_model.metadata or {})
|
||||
# 持久化完整候选列表到 extra_meta
|
||||
meta = dict(_cover_model.extra_meta or {})
|
||||
meta["cover_candidates"] = cover_candidates
|
||||
_cover_model.metadata = meta
|
||||
_cover_model.extra_meta = meta
|
||||
_cover_session.commit()
|
||||
logger.info(
|
||||
"[task_id=%s] 封面帧已持久化(ffmpeg本地抽帧): cover_url=%s candidates=%d",
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
"""防回归测试:P1 修复
|
||||
- Bug 1: gen_task 过期内存对象 _repo.update() 覆盖 DB status 为 pending
|
||||
- Bug 2: 封面模型误用 .metadata(SQLAlchemy 保留属性),应为 .extra_meta
|
||||
"""
|
||||
import ast
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
GENERATION_FILE = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "apps"
|
||||
/ "worker"
|
||||
/ "worker_app"
|
||||
/ "tasks"
|
||||
/ "generation.py"
|
||||
)
|
||||
|
||||
|
||||
def _read_source() -> str:
|
||||
return GENERATION_FILE.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
class TestCoverModelUsesExtraMeta:
|
||||
"""封面持久化必须使用 ORM 属性 extra_meta,而不是 SQLAlchemy 保留的 .metadata。"""
|
||||
|
||||
def test_no_metadata_attribute_access_on_cover_model(self):
|
||||
source = _read_source()
|
||||
# 禁止对 _cover_model.metadata 进行读或写
|
||||
assert "_cover_model.metadata" not in source, (
|
||||
"_cover_model.metadata is the SQLAlchemy reserved MetaData object, "
|
||||
"not the JSON column. Use _cover_model.extra_meta instead."
|
||||
)
|
||||
|
||||
def test_extra_meta_used_for_cover_candidates(self):
|
||||
source = _read_source()
|
||||
assert "_cover_model.extra_meta" in source
|
||||
assert 'meta["cover_candidates"]' in source
|
||||
|
||||
|
||||
class TestAssetAnalysesDoesNotOverwriteStatus:
|
||||
"""保存 asset_analyses 时不能用过期的 gen_task 内存对象整体 _repo.update,
|
||||
否则会把已被 _update_task_status 改为 running 的 status 覆盖回 pending。"""
|
||||
|
||||
def test_no_stale_repo_update_with_gen_task(self):
|
||||
source = _read_source()
|
||||
# 旧代码:gen_task.extra_meta = {...}; _repo.update(gen_task)
|
||||
# 这行会把内存中的 pending status 写回 DB
|
||||
assert "_repo.update(gen_task)" not in source, (
|
||||
"_repo.update(gen_task) writes a stale in-memory object back to DB, "
|
||||
"overwriting status set by _update_task_status. "
|
||||
"Use an independent session to update only extra_meta."
|
||||
)
|
||||
|
||||
def test_asset_analyses_uses_independent_session(self):
|
||||
"""asset_analyses 持久化必须用独立 session 查询最新模型再提交。"""
|
||||
source = _read_source()
|
||||
assert "_meta_session" in source
|
||||
assert "GenerationTaskModel" in source
|
||||
# 必须只更新 extra_meta 字段
|
||||
assert 'existing["asset_analyses"]' in source
|
||||
|
||||
|
||||
class TestGenerationTaskModelOrmAttribute:
|
||||
"""确认 ORM 属性映射:Python 属性 extra_meta -> DB 列 metadata。"""
|
||||
|
||||
def test_orm_attribute_is_extra_meta(self):
|
||||
from packages.adapters.sqlalchemy_impl.models import GenerationTaskModel
|
||||
|
||||
# ORM 属性必须存在
|
||||
assert hasattr(GenerationTaskModel, "extra_meta")
|
||||
# .metadata 是 SQLAlchemy 声明基类保留的 MetaData,不是列描述符
|
||||
# 它不应该是我们的 JSON 字段
|
||||
from sqlalchemy import MetaData
|
||||
|
||||
assert isinstance(GenerationTaskModel.metadata, MetaData)
|
||||
Reference in New Issue
Block a user