abc54ace4e
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 13s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 50s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
P0 hotfix: ingest_jobs.library_id (and all other UUID fields across 10 tables) were varchar(32), but standard UUIDs with hyphens are 36 chars, causing StringDataRightTruncation on insert — mini-program upload was completely broken. Changes: - models.py: All String(32) UUID fields → String(36) across 10 tables (projects, edit_templates, edit_plans, template_clip_configs, edit_plan_clips, ingest_jobs, classification_jobs, generation_tasks, generated_videos, jobs) - Alembic migration 036: ALTER COLUMN for all affected fields - Unit tests: 43 tests verifying String(36) on all UUID columns and standard UUID construction with hyphens Fixes: upload/direct/complete StringDataRightTruncation error
205 lines
7.8 KiB
Python
205 lines
7.8 KiB
Python
"""UUID 字段长度单元测试 — 验证标准 UUID(带横杠,36字符)可正常插入.
|
||
|
||
覆盖:
|
||
- 所有从 varchar(32) 扩到 varchar(36) 的表
|
||
- 插入标准 UUID 格式(带横杠)不报 StringDataRightTruncation
|
||
- 字段长度从 32 扩到 36 后 ORM 模型定义正确
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import uuid
|
||
|
||
import pytest
|
||
|
||
from packages.adapters.sqlalchemy_impl.models import (
|
||
Base,
|
||
ClassificationJobModel,
|
||
EditPlanClipModel,
|
||
EditPlanModel,
|
||
EditTemplateModel,
|
||
GeneratedVideoModel,
|
||
GenerationTaskModel,
|
||
IngestJobModel,
|
||
JobModel,
|
||
ProjectModel,
|
||
TemplateClipConfigModel,
|
||
)
|
||
|
||
|
||
def _uuid() -> str:
|
||
"""生成标准 UUID 字符串(带横杠,36 字符)."""
|
||
return str(uuid.uuid4())
|
||
|
||
|
||
class TestUUIDFieldLengthModels:
|
||
"""验证 ORM 模型中 UUID 字段定义为 String(36)."""
|
||
|
||
def _get_column_type_length(self, model_cls, column_name: str) -> int:
|
||
"""获取模型列的 String 长度."""
|
||
col = model_cls.__table__.columns[column_name]
|
||
return col.type.length
|
||
|
||
# ── ProjectModel ──────────────────────────────────────────────────────────
|
||
|
||
def test_project_id_is_36(self):
|
||
assert self._get_column_type_length(ProjectModel, "id") == 36
|
||
|
||
def test_project_owner_user_id_is_36(self):
|
||
assert self._get_column_type_length(ProjectModel, "owner_user_id") == 36
|
||
|
||
# ── EditTemplateModel ─────────────────────────────────────────────────────
|
||
|
||
def test_edit_template_id_is_36(self):
|
||
assert self._get_column_type_length(EditTemplateModel, "id") == 36
|
||
|
||
# ── EditPlanModel ─────────────────────────────────────────────────────────
|
||
|
||
@pytest.mark.parametrize(
|
||
"col",
|
||
["id", "template_id", "source_edit_plan_id", "project_id", "created_by_user_id"],
|
||
)
|
||
def test_edit_plan_uuid_fields_are_36(self, col):
|
||
assert self._get_column_type_length(EditPlanModel, col) == 36
|
||
|
||
# ── TemplateClipConfigModel ───────────────────────────────────────────────
|
||
|
||
@pytest.mark.parametrize("col", ["id", "template_id"])
|
||
def test_template_clip_config_uuid_fields_are_36(self, col):
|
||
assert self._get_column_type_length(TemplateClipConfigModel, col) == 36
|
||
|
||
# ── EditPlanClipModel ─────────────────────────────────────────────────────
|
||
|
||
@pytest.mark.parametrize(
|
||
"col",
|
||
["id", "plan_id", "template_clip_config_id", "asset_id"],
|
||
)
|
||
def test_edit_plan_clip_uuid_fields_are_36(self, col):
|
||
assert self._get_column_type_length(EditPlanClipModel, col) == 36
|
||
|
||
# ── IngestJobModel(P0 bug 所在表)─────────────────────────────────────────
|
||
|
||
@pytest.mark.parametrize(
|
||
"col",
|
||
["id", "project_id", "library_id", "result_asset_id"],
|
||
)
|
||
def test_ingest_job_uuid_fields_are_36(self, col):
|
||
assert self._get_column_type_length(IngestJobModel, col) == 36
|
||
|
||
def test_ingest_job_library_id_accepts_standard_uuid(self):
|
||
"""P0 回归:library_id 必须能容纳标准 UUID(36 字符带横杠)."""
|
||
standard_uuid = "550e8400-e29b-41d4-a716-446655440000"
|
||
assert len(standard_uuid) == 36
|
||
col = IngestJobModel.__table__.columns["library_id"]
|
||
assert col.type.length >= 36
|
||
|
||
# ── ClassificationJobModel ────────────────────────────────────────────────
|
||
|
||
@pytest.mark.parametrize("col", ["id", "project_id", "asset_id"])
|
||
def test_classification_job_uuid_fields_are_36(self, col):
|
||
assert self._get_column_type_length(ClassificationJobModel, col) == 36
|
||
|
||
# ── GenerationTaskModel ───────────────────────────────────────────────────
|
||
|
||
@pytest.mark.parametrize(
|
||
"col",
|
||
[
|
||
"id",
|
||
"project_id",
|
||
"strategy_id",
|
||
"asset_library_id",
|
||
"voice_library_id",
|
||
"created_by_user_id",
|
||
"source_edit_plan_id",
|
||
"batch_id",
|
||
],
|
||
)
|
||
def test_generation_task_uuid_fields_are_36(self, col):
|
||
assert self._get_column_type_length(GenerationTaskModel, col) == 36
|
||
|
||
# ── GeneratedVideoModel ───────────────────────────────────────────────────
|
||
|
||
@pytest.mark.parametrize(
|
||
"col",
|
||
["id", "project_id", "generation_task_id", "duplicate_of"],
|
||
)
|
||
def test_generated_video_uuid_fields_are_36(self, col):
|
||
assert self._get_column_type_length(GeneratedVideoModel, col) == 36
|
||
|
||
# ── JobModel ──────────────────────────────────────────────────────────────
|
||
|
||
@pytest.mark.parametrize(
|
||
"col",
|
||
["id", "project_id", "source_id", "created_by_user_id"],
|
||
)
|
||
def test_job_uuid_fields_are_36(self, col):
|
||
assert self._get_column_type_length(JobModel, col) == 36
|
||
|
||
|
||
class TestUUIDInsertWithHyphens:
|
||
"""验证标准 UUID(带横杠)可构造 ORM 对象,字段长度足够."""
|
||
|
||
def test_ingest_job_model_construct_with_standard_uuid(self):
|
||
"""P0 回归测试:用标准 UUID 构造 IngestJobModel 不报错."""
|
||
std_uuid = _uuid()
|
||
assert len(std_uuid) == 36
|
||
|
||
job = IngestJobModel(
|
||
id=_uuid(),
|
||
project_id=_uuid(),
|
||
library_id=std_uuid, # ← 关键:标准 UUID 带横杠
|
||
storage_key="test/file.mp4",
|
||
result_asset_id=_uuid(),
|
||
)
|
||
assert job.library_id == std_uuid
|
||
assert len(job.library_id) == 36
|
||
|
||
def test_project_model_construct_with_standard_uuid(self):
|
||
project = ProjectModel(
|
||
id=_uuid(),
|
||
owner_user_id=_uuid(),
|
||
name="test",
|
||
)
|
||
assert len(project.id) == 36
|
||
assert len(project.owner_user_id) == 36
|
||
|
||
def test_generation_task_model_construct_with_standard_uuid(self):
|
||
task = GenerationTaskModel(
|
||
id=_uuid(),
|
||
project_id=_uuid(),
|
||
asset_library_id=_uuid(),
|
||
created_by_user_id=_uuid(),
|
||
source_edit_plan_id=_uuid(),
|
||
batch_id=_uuid(),
|
||
)
|
||
assert len(task.id) == 36
|
||
assert len(task.asset_library_id) == 36
|
||
|
||
def test_edit_plan_model_construct_with_standard_uuid(self):
|
||
plan = EditPlanModel(
|
||
id=_uuid(),
|
||
template_id=_uuid(),
|
||
source_edit_plan_id=_uuid(),
|
||
project_id=_uuid(),
|
||
created_by_user_id=_uuid(),
|
||
)
|
||
assert len(plan.id) == 36
|
||
assert len(plan.template_id) == 36
|
||
|
||
def test_generated_video_model_construct_with_standard_uuid(self):
|
||
video = GeneratedVideoModel(
|
||
id=_uuid(),
|
||
project_id=_uuid(),
|
||
generation_task_id=_uuid(),
|
||
duplicate_of=_uuid(),
|
||
name="test.mp4",
|
||
file_url="https://example.com/test.mp4",
|
||
file_size=1000,
|
||
duration=10.0,
|
||
width=1280,
|
||
height=720,
|
||
fps=25.0,
|
||
)
|
||
assert len(video.id) == 36
|
||
assert len(video.duplicate_of) == 36
|