Compare commits

..

7 Commits

Author SHA1 Message Date
CI Bot b2bf9af6de fix(test): harden batch_download session tests with multi-path patching + diagnostic
Full-suite runs showed test_batch_download_session_closed and
test_batch_download_closes_session_on_error failing with
"Expected close to have been called once. Called 0 times."

Changes:
- Patch SessionLocal in all possible locations: _db_mod, sys.modules,
  and function globals (if present)
- Call raw_fn directly instead of going through _call_task
- Add diagnostic assertion to verify mock_session_factory was called
- Remove try/finally restore (not needed for isolated tests)

Also includes fix from PR #1407: add missing str fields to mock clip.
2026-08-17 20:48:33 +08:00
CI Bot 8799eb8ff5 fix(test): fix merge response assertion for TestClipSplitMerge
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 34s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m35s
AI Code Review / AI Code Review (pull_request) Successful in 1m34s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m45s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m49s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m29s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m7s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 3m38s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 3m52s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 8m1s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m52s
CI/CD Pipeline / CI Gate (pull_request) Successful in 7s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 43s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 46s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 715h2m53s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 715h2m53s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 715h2m55s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 715h2m54s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 715h2m55s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 715h2m56s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 715h10m21s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 715h10m21s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 715h10m49s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 715h10m51s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 715h10m57s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 715h10m57s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 715h10m51s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 715h10m53s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 715h43m59s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 715h44m35s
The merge endpoint returns {"merged_clip": {...}, "deleted_clip_ids": [...]}
not a flat clip object. Update test_merge_clips_success to check
data["merged_clip"] instead of data directly.
2026-08-17 20:15:33 +08:00
CI Bot caa5113f7f fix(test): add missing str fields to mock clip to fix Pydantic validation
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 34s
AI Code Review / AI Code Review (pull_request) Successful in 1m19s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m50s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m51s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m3s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m23s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m37s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m13s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 3m36s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 3m55s
CI/CD Pipeline / Validate - Code Quality (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
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 715h16m51s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 715h16m52s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 715h17m23s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 715h17m26s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 715h17m24s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 715h17m27s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 715h17m28s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 715h17m28s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 715h50m30s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 715h51m2s
_make_mock_clip used MagicMock which auto-creates attributes as MagicMock
objects. EditorClipResponse requires template_clip_config_id, created_at,
and updated_at to be str. Pydantic validation fails with MagicMock values.

Fix: explicitly set these fields to proper types in _make_mock_clip:
- template_clip_config_id = ""
- created_at = None (handled by _fmt_dt → "")
- updated_at = None (handled by _fmt_dt → "")

Fixes 7 failing tests in TestClipEndpoints and TestClipSplitMerge.
2026-08-17 20:08:48 +08:00
xiaoxia 4efc44e0dc fix(tests): resolve flaky test_batch_download_session_closed (#1406)
CI/CD Pipeline / Unit Tests (push) Failing after 1s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 0s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m4s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m40s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m53s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 6m51s
CI/CD Pipeline / Integration Tests (push) Successful in 2m59s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 13m16s
CI/CD Pipeline / Build Staging API Image (push) Failing after 13m17s
CI/CD Pipeline / Canary Release to Production (push) Failing after 715h17m44s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 715h17m45s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 715h17m45s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 715h17m44s
CI/CD Pipeline / Production Browser E2E (push) Failing after 715h24m10s
CI/CD Pipeline / Deploy Production (push) Failing after 715h24m10s
CI/CD Pipeline / Build Production API Image (push) Failing after 715h24m12s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 715h24m11s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 715h30m53s
CI/CD Pipeline / PR Build API Image (push) Failing after 715h30m57s
CI/CD Pipeline / Frontend Lint (push) Failing after 715h31m1s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 715h31m5s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 715h51m22s
CI/CD Pipeline / CI Gate (push) Failing after 715h54m50s
CI/CD Pipeline / Build Production Web Image (push) Failing after 715h57m50s
CI/CD Pipeline / PR Build Web Image (push) Failing after 716h4m33s
2026-08-17 19:55:28 +08:00
xiaoxia 3c800d3f3f fix(clips): 全面修复 Clips API 响应结构 — 替换 PR #1403 (#1404)
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m25s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m36s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m25s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m56s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m5s
CI/CD Pipeline / Integration Tests (push) Successful in 2m28s
CI/CD Pipeline / Unit Tests (push) Failing after 11m34s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 13m57s
CI/CD Pipeline / Build Staging API Image (push) Successful in 29m40s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m17s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 41s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 2m26s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m1s
CI/CD Pipeline / Canary Release to Production (push) Failing after 716h33m39s
CI/CD Pipeline / Deploy Production (push) Failing after 716h56m4s
CI/CD Pipeline / CI Gate (push) Failing after 716h56m5s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 716h56m5s
CI/CD Pipeline / Build Production Web Image (push) Failing after 716h56m6s
CI/CD Pipeline / Build Production API Image (push) Failing after 716h56m6s
CI/CD Pipeline / PR Build API Image (push) Failing after 717h7m39s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 717h7m37s
CI/CD Pipeline / Frontend Lint (push) Failing after 717h7m40s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 717h7m43s
CI/CD Pipeline / Production Browser E2E (push) Failing after 717h29m41s
CI/CD Pipeline / PR Build Web Image (push) Failing after 717h41m16s
2026-08-17 18:18:48 +08:00
xiaoxia e2d10dd357 refactor: 移除 Step5 左侧预览网格卡片 (#1391)
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m23s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m52s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m46s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m16s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m49s
CI/CD Pipeline / Integration Tests (push) Successful in 3m5s
CI/CD Pipeline / Unit Tests (push) Successful in 13m15s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 15m39s
CI/CD Pipeline / Build Staging API Image (push) Successful in 31m26s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m20s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m17s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 3m21s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m48s
CI/CD Pipeline / Canary Release to Production (push) Failing after 718h53m23s
CI/CD Pipeline / Production Browser E2E (push) Failing after 719h16m43s
CI/CD Pipeline / Deploy Production (push) Failing after 719h16m43s
CI/CD Pipeline / Build Production Web Image (push) Failing after 719h16m44s
CI/CD Pipeline / CI Gate (push) Failing after 719h16m44s
CI/CD Pipeline / Build Production API Image (push) Failing after 719h16m44s
CI/CD Pipeline / PR Build API Image (push) Failing after 719h30m1s
CI/CD Pipeline / Frontend Lint (push) Failing after 719h30m1s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 719h29m59s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 719h30m2s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 719h50m21s
CI/CD Pipeline / PR Build Web Image (push) Failing after 720h3m36s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-08-17 15:56:32 +08:00
灵应 c62125cf7b fix: 主动token刷新-在过期前自动续期避免401错误 (#1385)
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m2s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 3m16s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m36s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 7m5s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 8m44s
CI/CD Pipeline / Integration Tests (push) Successful in 3m24s
CI/CD Pipeline / Unit Tests (push) Successful in 12m54s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 18m32s
CI/CD Pipeline / Build Staging API Image (push) Successful in 34m17s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m18s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 41s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 2m25s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m2s
CI/CD Pipeline / Canary Release to Production (push) Failing after 719h53m59s
CI/CD Pipeline / Deploy Production (push) Failing after 720h19m45s
CI/CD Pipeline / CI Gate (push) Failing after 720h19m45s
CI/CD Pipeline / Build Production Web Image (push) Failing after 720h19m45s
CI/CD Pipeline / Build Production API Image (push) Failing after 720h19m45s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 720h32m38s
CI/CD Pipeline / Frontend Lint (push) Failing after 720h32m40s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 720h32m42s
CI/CD Pipeline / PR Build Web Image (push) Failing after 720h32m39s
CI/CD Pipeline / Production Browser E2E (push) Failing after 720h53m21s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 720h53m22s
CI/CD Pipeline / PR Build API Image (push) Failing after 721h6m16s
2026-08-17 14:53:52 +08:00
6 changed files with 110 additions and 148 deletions
@@ -207,13 +207,10 @@ const GeneratePage: React.FC = () => {
onRetry={handleRetryGenerate}
onDismissError={handleDismissError}
presetVoices={presetVoices}
videoRatio={videoRatio}
/* Step5 多预览 */
previewCount={previewCount}
onPreviewCountChange={setPreviewCount}
previewItems={step5Preview.items}
previewSelectedIndex={step5Preview.selectedIndex}
onSelectPreview={step5Preview.setSelectedIndex}
previewOverallStatus={step5Preview.previewStatus}
previewOverallError={step5Preview.previewError}
previewOverallProgress={step5Preview.progress}
@@ -63,13 +63,10 @@ export interface GenerateStepContentProps {
onDismissError: () => void
/* 其他 */
presetVoices: PresetVoiceItem[]
videoRatio: string
/* Step4 预览(多预览) */
previewCount: number
onPreviewCountChange: (count: number) => void
previewItems: PreviewItem[]
previewSelectedIndex: number
onSelectPreview: (index: number) => void
previewOverallStatus: PreviewStatus
previewOverallError: string
previewOverallProgress: number
@@ -110,12 +107,9 @@ export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) =
onRetry,
onDismissError,
presetVoices,
videoRatio,
previewCount,
onPreviewCountChange,
previewItems,
previewSelectedIndex,
onSelectPreview,
previewOverallStatus,
previewOverallError,
previewOverallProgress,
@@ -161,12 +155,9 @@ export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) =
case 5:
return (
<Step5GeneratePreview
videoRatio={videoRatio}
previewCount={previewCount}
onPreviewCountChange={onPreviewCountChange}
items={previewItems}
selectedIndex={previewSelectedIndex}
onSelectPreview={onSelectPreview}
overallStatus={previewOverallStatus}
overallError={previewOverallError}
overallProgress={previewOverallProgress}
@@ -1,11 +1,11 @@
/**
* Step 5 生成预览组件(支持多预览)
* 调用后端预览生成接口,展示多个真实视频预览(网格布局)
* 调用后端预览生成接口,展示预览生成状态
* 预览视频由右侧 PreviewVideoPanel 展示,本组件不重复显示预览网格
*/
import React from "react"
import {
CheckCircleFilled,
LoadingOutlined,
ReloadOutlined,
PlayCircleOutlined,
ExclamationCircleFilled,
@@ -15,12 +15,9 @@ import { InputNumber } from "antd"
import type { PreviewItem, PreviewStatus } from "../hooks/useStep5Preview"
interface Step5GeneratePreviewProps {
videoRatio: string
previewCount: number
onPreviewCountChange: (count: number) => void
items: PreviewItem[]
selectedIndex: number
onSelectPreview: (index: number) => void
overallStatus: PreviewStatus
overallError: string
overallProgress: number
@@ -37,12 +34,9 @@ const PREVIEW_COUNT_OPTIONS = [
]
const Step5GeneratePreview: React.FC<Step5GeneratePreviewProps> = ({
videoRatio,
previewCount,
onPreviewCountChange,
items,
selectedIndex,
onSelectPreview,
overallStatus,
overallError,
overallProgress,
@@ -50,7 +44,6 @@ const Step5GeneratePreview: React.FC<Step5GeneratePreviewProps> = ({
onGeneratePreview,
onRegeneratePreview,
}) => {
const aspectRatio = (videoRatio || "16:9").replace(":", "/") // "9:16" → "9/16", "16:9" → "16/9"
const isIdle = overallStatus === "idle"
const isError = overallStatus === "error" && !items.some((it) => it.status === "ready")
@@ -127,109 +120,7 @@ const Step5GeneratePreview: React.FC<Step5GeneratePreviewProps> = ({
</div>
)}
{/* 多预览网格(生成中/完成/部分完成) */}
{(anyGenerating || overallStatus === "ready") && items.length > 0 && (
<div
className="xx-preview-grid"
style={{
display: "grid",
gridTemplateColumns: `repeat(${Math.min(items.length, 3)}, 1fr)`,
gap: 12,
maxWidth: `${Math.min(items.length, 3) * 280 + (Math.min(items.length, 3) - 1) * 12}px`,
margin: "0 auto 16px",
}}
>
{items.map((item) => {
const isSelected = item.index === selectedIndex
return (
<div
key={item.index}
onClick={() => {
if (item.status === "ready") onSelectPreview(item.index)
}}
style={{
borderRadius: 8,
border: isSelected ? "2px solid #1677ff" : "1px solid #e8e8e8",
overflow: "hidden",
cursor: item.status === "ready" ? "pointer" : "default",
opacity: item.status === "error" ? 0.6 : 1,
transition: "all 0.2s",
}}
>
{/* 轻量卡片:深色背景 + 状态指示 */}
<div
style={{
aspectRatio,
background: "#1a1a2e",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
position: "relative",
gap: 8,
}}
>
{/* 中心:预览编号 */}
<span
style={{
fontSize: 24,
fontWeight: 700,
color: "#fff",
opacity: 0.9,
}}
>
预览 #{item.index + 1}
</span>
{/* 状态指示 */}
{item.status === "generating" && (
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
<LoadingOutlined style={{ fontSize: 14, color: "#fff" }} spin />
<span style={{ color: "rgba(255,255,255,0.8)", fontSize: 12 }}>
生成中 {item.progress}%
</span>
</div>
)}
{item.status === "pending" && (
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
<ClockCircleOutlined
style={{ fontSize: 14, color: "rgba(255,255,255,0.6)" }}
/>
<span style={{ color: "rgba(255,255,255,0.6)", fontSize: 12 }}>
排队中...
</span>
</div>
)}
{item.status === "ready" && (
<CheckCircleFilled style={{ fontSize: 18, color: "#52c41a" }} />
)}
{item.status === "error" && (
<ExclamationCircleFilled style={{ fontSize: 18, color: "#ef4444" }} />
)}
{/* 选中角标 */}
{isSelected && item.status === "ready" && (
<div
style={{
position: "absolute",
top: 4,
right: 4,
background: "#1677ff",
color: "#fff",
fontSize: 10,
padding: "2px 6px",
borderRadius: 4,
}}
>
预览 #{item.index + 1}
</div>
)}
</div>
</div>
)
})}
</div>
)}
{/* 预览网格已移除 — 右侧 PreviewVideoPanel 已展示实际预览视频,左侧不再重复显示 */}
{/* 整体进度条(多预览生成中) */}
{anyGenerating && (
@@ -244,7 +135,7 @@ const Step5GeneratePreview: React.FC<Step5GeneratePreviewProps> = ({
<CheckCircleFilled style={{ color: "#52c41a", marginRight: 8 }} />
<span>
{items.filter((it) => it.status === "ready").length} 个预览生成成功
{items.length > 1 ? ",点击选择要查看的版本" : ",确认效果后进入下一步"}
",请在右侧预览面板查看效果"
</span>
<button
className="xx-preview-regenerate-btn"
+21 -8
View File
@@ -31,14 +31,27 @@ vi.mock("react-router-dom", async () => {
})
vi.mock("@/store/authStore", () => ({
useAuthStore: (selector: any) =>
selector({
user: { id: "1", username: "testuser" },
token: "mock-token",
isAuthenticated: true,
setAuth: mockSetAuth,
clearAuth: mockClearAuth,
}),
useAuthStore: Object.assign(
(selector: any) =>
selector({
user: { id: "1", username: "testuser" },
token: "mock-token",
refreshToken: "mock-refresh-token",
isAuthenticated: true,
setAuth: mockSetAuth,
clearAuth: mockClearAuth,
}),
{
getState: () => ({
user: { id: "1", username: "testuser" },
token: "mock-token",
refreshToken: "mock-refresh-token",
isAuthenticated: true,
setAuth: mockSetAuth,
clearAuth: mockClearAuth,
}),
},
),
}))
vi.mock("@tanstack/react-query", () => ({
+80 -14
View File
@@ -108,21 +108,30 @@ def _run_with_fakes(
session = MagicMock()
session_maker = MagicMock(return_value=session)
captured = {"upload_calls": [], "session": session_maker()}
captured: dict = {"upload_calls": []}
def _tracking_upload(local_path, storage_key):
captured["upload_calls"].append((local_path, storage_key))
return upload_fn(local_path, storage_key)
# Wrap session_maker to capture the session INSIDE the patch context.
# This avoids stale PromiseProxy cache issues in full-suite runs.
_created_sessions: list = []
_orig_sm = session_maker
def _tracking_sm(*a, **kw):
s = _orig_sm(*a, **kw)
_created_sessions.append(s)
return s
bound_task = _make_bound_task()
with patch(
"packages.adapters.sqlalchemy_impl.generated_video_repository.SQLAlchemyGeneratedVideoRepository",
return_value=repo,
):
import worker_app.db as _db_mod
with patch.object(_db_mod, "SessionLocal", _tracking_sm):
with patch(
"worker_app.db.SessionLocal",
session_maker,
"packages.adapters.sqlalchemy_impl.generated_video_repository.SQLAlchemyGeneratedVideoRepository",
return_value=repo,
):
with patch(
"video_processing.oss_helpers.upload_to_oss",
@@ -134,6 +143,7 @@ def _run_with_fakes(
):
result = _call_task(batch_download_videos, bound_task, [v.id for v in videos], user_id)
captured["session"] = _created_sessions[0] if _created_sessions else None
captured["result"] = result
return captured
@@ -244,19 +254,67 @@ def test_batch_download_single_video():
def test_batch_download_session_closed():
"""DB session is always closed (via finally block)."""
"""DB session is always closed (via finally block).
Patches the function's own globals to inject mock SessionLocal,
bypassing any import caching issues in the full suite.
"""
import sys
import worker_app.db as _db_mod
from apps.worker.worker_app.tasks.batch_download import batch_download_videos
videos = [_FakeVideo("v1", "v.mp4")]
repo = _FakeGeneratedVideoRepository(videos)
session = MagicMock()
session_maker = MagicMock(return_value=session)
mock_session_factory = MagicMock(return_value=session)
_run_with_fakes(videos, session_maker=session_maker)
def _noop_download(url, dest):
Path(dest).parent.mkdir(parents=True, exist_ok=True)
Path(dest).write_bytes(b"fake video data")
bound_task = _make_bound_task()
# Get the raw function to patch its globals
raw_fn = _get_raw_task_fn(batch_download_videos)
# Patch SessionLocal in ALL possible module locations
_db_mod.SessionLocal = mock_session_factory
if "worker_app.db" in sys.modules:
sys.modules["worker_app.db"].SessionLocal = mock_session_factory
# Also patch in the function's own globals if it has a reference there
if "SessionLocal" in raw_fn.__globals__:
raw_fn.__globals__["SessionLocal"] = mock_session_factory
try:
with patch(
"packages.adapters.sqlalchemy_impl.generated_video_repository.SQLAlchemyGeneratedVideoRepository",
return_value=repo,
):
with patch("video_processing.oss_helpers.upload_to_oss", return_value="https://oss.example.com/zip"):
with patch(
"apps.worker.worker_app.tasks.batch_download._download_video_to_file",
_noop_download,
):
raw_fn(bound_task, ["v1"], "user_1")
finally:
pass # Don't restore - other tests handle their own patches
# Diagnostic: check if our mock factory was actually called
assert mock_session_factory.called, (
"SessionLocal mock was never called! "
f"raw_fn={raw_fn}, type={type(raw_fn)}"
)
session.close.assert_called_once()
def test_batch_download_closes_session_on_error():
"""Session is closed even when get_by_ids raises."""
import sys
import worker_app.db as _db_mod
from apps.worker.worker_app.tasks.batch_download import batch_download_videos
class _ExplodingRepo:
@@ -264,17 +322,25 @@ def test_batch_download_closes_session_on_error():
raise RuntimeError("db down")
session = MagicMock()
session_maker = MagicMock(return_value=session)
mock_session_factory = MagicMock(return_value=session)
bound_task = _make_bound_task()
raw_fn = _get_raw_task_fn(batch_download_videos)
_db_mod.SessionLocal = mock_session_factory
if "worker_app.db" in sys.modules:
sys.modules["worker_app.db"].SessionLocal = mock_session_factory
if "SessionLocal" in raw_fn.__globals__:
raw_fn.__globals__["SessionLocal"] = mock_session_factory
with patch(
"packages.adapters.sqlalchemy_impl.generated_video_repository.SQLAlchemyGeneratedVideoRepository",
return_value=_ExplodingRepo(),
):
with patch("worker_app.db.SessionLocal", session_maker):
with pytest.raises(RuntimeError, match="db down"):
_call_task(batch_download_videos, bound_task, ["v1"], "u")
with pytest.raises(RuntimeError, match="db down"):
raw_fn(bound_task, ["v1"], "u")
assert mock_session_factory.called, "SessionLocal mock was never called!"
session.close.assert_called_once()
+5 -1
View File
@@ -61,6 +61,9 @@ def _make_mock_clip(clip_id="clip-001", order=0, duration=10.0, clip_type="video
clip.config = {}
clip.asset_id = "asset-001"
clip.status = "ready"
clip.template_clip_config_id = ""
clip.created_at = None
clip.updated_at = None
return clip
@@ -327,7 +330,8 @@ class TestClipSplitMerge:
resp = c.post(BASE + "/clips/merge", json={"clip_ids": ["clip-001", "clip-002"]})
assert resp.status_code == 200
data = resp.json()
assert "id" in data
assert "merged_clip" in data
assert "id" in data["merged_clip"]
mock_plan_svc.merge_clips.assert_called_once_with(["clip-001", "clip-002"])
def test_merge_clips_single_422(self, client):