"""
素材 CRUD API 集成测试

覆盖端点：
- POST   /assets              — 创建素材
- GET    /assets              — 获取素材列表
- GET    /assets/{id}         — 获取单个素材详情
- PUT    /assets/{id}         — 更新素材
- DELETE /assets/{id}         — 删除素材
- POST   /assets/batch-delete — 批量删除素材
- POST   /assets/{id}/tags    — 素材打标签
- DELETE /assets/{id}/tags/{tag_id} — 移除标签

使用 FastAPI TestClient + dependency_overrides 模式，
导入真实模块，mock 外部依赖。
"""

from __future__ import annotations

import os
import sys
from pathlib import Path
from unittest.mock import MagicMock

# ── 环境变量 & sys.path（必须在导入 app.* 之前设置） ──────────────────────────
os.environ.setdefault("JWT_SECRET_KEY", "unit-test-secret-key-for-testing")
os.environ.setdefault("DATABASE_URL", "sqlite:///test.db")

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api"))

from app.api.routes.assets import router
from app.auth import AuthenticatedUser, get_current_user
from app.core.storage import get_storage_service
from app.dependencies import (
    get_asset_library_repository,
    get_asset_repository,
    get_project_repository,
    get_tag_repository,
)

from packages.domain import (
    Asset,
    AssetLibrary,
    AssetLibraryKind,
    AssetStatus,
    ClassificationStatus,
    Project,
    Tag,
    User,
)

# ---------------------------------------------------------------------------
# 1. Stub Repository 实现
# ---------------------------------------------------------------------------


class StubProjectRepository:
    def __init__(self, projects: dict[str, Project] | None = None):
        self._projects = projects or {}

    def find_by_id(self, project_id: str) -> Project | None:
        return self._projects.get(project_id)

    def find_accessible_projects(self, user_id: str) -> list[Project]:
        return [p for p in self._projects.values() if p.can_access(user_id)]

    def count_by_owner(self, owner_user_id: str) -> int:
        return len([p for p in self._projects.values() if p.owner_user_id == owner_user_id])


class StubAssetLibraryRepository:
    def __init__(self, libraries: dict[str, AssetLibrary] | None = None):
        self._libraries = libraries or {}

    def get(self, library_id: str) -> AssetLibrary | None:
        return self._libraries.get(library_id)

    def find_by_id(self, library_id: str) -> AssetLibrary | None:
        return self._libraries.get(library_id)

    def find_by_project(self, project_id: str, kind=None) -> list[AssetLibrary]:
        items = [lib for lib in self._libraries.values() if lib.project_id == project_id]
        if kind is not None:
            items = [lib for lib in items if lib.kind == kind]
        return items


class StubAssetRepository:
    def __init__(self, assets: dict[str, Asset] | None = None):
        self._assets = assets or {}

    def create(self, asset: Asset) -> Asset:
        self._assets[asset.id] = asset
        return asset

    def get(self, asset_id: str) -> Asset | None:
        return self._assets.get(asset_id)

    def find_by_id(self, asset_id: str) -> Asset | None:
        return self._assets.get(asset_id)

    def find_by_library(
        self, library_id: str, skip: int = 0, limit: int = 100, status: list[str] | None = None
    ) -> list[Asset]:
        items = [a for a in self._assets.values() if a.library_id == library_id]
        if status:
            items = [a for a in items if a.status.value in status]
        items.sort(key=lambda a: a.created_at, reverse=True)
        return items[skip : skip + limit]

    def find_by_library_and_file_type(
        self, library_id: str, file_type: str, skip: int = 0, limit: int = 100, status: list[str] | None = None
    ) -> list[Asset]:
        items = [
            a
            for a in self._assets.values()
            if a.library_id == library_id and a.mime_type and a.mime_type.startswith(file_type)
        ]
        if status:
            items = [a for a in items if a.status.value in status]
        items.sort(key=lambda a: a.created_at, reverse=True)
        return items[skip : skip + limit]

    def find_by_project(
        self, project_id: str, skip: int = 0, limit: int = 100, status: list[str] | None = None
    ) -> list[Asset]:
        items = [a for a in self._assets.values() if a.project_id == project_id]
        if status:
            items = [a for a in items if a.status.value in status]
        items.sort(key=lambda a: a.created_at, reverse=True)
        return items[skip : skip + limit]

    def update(self, asset: Asset) -> Asset:
        self._assets[asset.id] = asset
        return asset

    def delete(self, asset_id: str) -> bool:
        """硬删除（与真实实现一致）。"""
        if asset_id in self._assets:
            del self._assets[asset_id]
            return True
        return False

    def batch_delete(self, asset_ids: list[str]) -> int:
        """软删除：标记 status=deleted。"""
        from datetime import datetime, timezone

        from packages.domain import AssetStatus

        count = 0
        for aid in asset_ids:
            asset = self._assets.get(aid)
            if asset and asset.status != AssetStatus.DELETED:
                asset.status = AssetStatus.DELETED
                asset.updated_at = datetime.now(timezone.utc)
                count += 1
        return count

    def batch_update_metadata(self, asset_ids: list[str], metadata_patch: dict) -> int:
        from datetime import datetime, timezone

        count = 0
        for aid in asset_ids:
            asset = self._assets.get(aid)
            if asset:
                asset.metadata = {**asset.metadata, **metadata_patch}
                asset.updated_at = datetime.now(timezone.utc)
                count += 1
        return count

    def batch_add_tags(self, asset_ids: list[str], tag_ids: list[str]) -> int:
        from datetime import datetime, timezone

        count = 0
        for aid in asset_ids:
            asset = self._assets.get(aid)
            if asset:
                changed = False
                for tid in tag_ids:
                    if tid not in asset.tag_ids:
                        asset.tag_ids.append(tid)
                        changed = True
                if changed:
                    asset.updated_at = datetime.now(timezone.utc)
                    count += 1
        return count

    def batch_replace_tags(self, asset_ids: list[str], tag_ids: list[str]) -> int:
        from datetime import datetime, timezone

        count = 0
        for aid in asset_ids:
            asset = self._assets.get(aid)
            if asset:
                asset.tag_ids = list(tag_ids)
                asset.updated_at = datetime.now(timezone.utc)
                count += 1
        return count

    def count_by_project(self, project_id: str, status: list[str] | None = None) -> int:
        items = [a for a in self._assets.values() if a.project_id == project_id]
        if status:
            items = [a for a in items if a.status.value in status]
        return len(items)

    def count_by_project_ids(self, project_ids: list[str], status: list[str] | None = None) -> int:
        items = [a for a in self._assets.values() if a.project_id in project_ids]
        if status:
            items = [a for a in items if a.status.value in status]
        return len(items)

    def find_by_library_and_file_hash(self, library_id: str, file_hash: str) -> Asset | None:
        if not file_hash:
            return None
        for asset in self._assets.values():
            if asset.library_id == library_id and getattr(asset, "file_hash", "") == file_hash:
                return asset
        return None


class StubTagRepository:
    def __init__(self, tags: dict[str, Tag] | None = None):
        self._tags = tags or {}

    def get(self, tag_id: str) -> Tag | None:
        return self._tags.get(tag_id)

    def create(self, tag: Tag) -> Tag:
        self._tags[tag.id] = tag
        return tag

    def list_by_user(self, user_id: str, skip: int = 0, limit: int = 100) -> list[Tag]:
        return [t for t in self._tags.values() if t.user_id == user_id][skip : skip + limit]


# ---------------------------------------------------------------------------
# 2. Helpers & Fixtures
# ---------------------------------------------------------------------------


def _make_user(**overrides) -> User:
    from datetime import datetime, timezone

    defaults = dict(
        id="user-test-001",
        email="test@example.com",
        display_name="Test User",
        username="testuser",
        subscription_plan="free",
        subscription_status="active",
        max_projects=3,
        max_storage_gb=10,
        created_at=datetime(2026, 1, 1, tzinfo=timezone.utc),
    )
    defaults.update(overrides)
    return User(**defaults)


def _direct_insert_asset(client, name="test-video.mp4", storage_key=None, mime_type="video/mp4", status=None):
    """Helper: insert asset directly into repo (bypass deprecated create_asset API)."""
    import uuid as _uuid

    app = client.app
    asset_repo = app.dependency_overrides[get_asset_repository]()
    kw = {}
    if status is not None:
        kw["status"] = status
    else:
        kw["status"] = AssetStatus.READY
    asset = Asset(
        id=_uuid.uuid4().hex,
        project_id="proj-1",
        library_id="lib-1",
        name=name,
        storage_key=storage_key or f"uploads/{name}",
        mime_type=mime_type,
        **kw,
    )
    asset_repo.create(asset)
    return asset.id


def _make_project(id: str = "proj-1", owner_user_id: str = "user-test-001") -> Project:
    return Project(id=id, name="Test Project", owner_user_id=owner_user_id)


def _make_library(id: str = "lib-1", project_id: str = "proj-1") -> AssetLibrary:
    return AssetLibrary(
        id=id,
        name="Test Video Library",
        project_id=project_id,
        kind=AssetLibraryKind.VIDEO,
    )


def _make_asset(**overrides) -> Asset:
    defaults = dict(
        id="asset-1",
        project_id="proj-1",
        library_id="lib-1",
        name="test-video.mp4",
        storage_key="uploads/test-video.mp4",
        mime_type="video/mp4",
        file_size=1024 * 1024,
        status=AssetStatus.READY,
        classification_status=ClassificationStatus.COMPLETED,
        uploaded_by_user_id="user-test-001",
        duration=30.5,
        width=1920,
        height=1080,
        fps=25.0,
        quality_score=85.0,
    )
    defaults.update(overrides)
    return Asset(**defaults)


def _make_tag(id: str = "tag-1", user_id: str = "user-test-001", name: str = "精彩片段") -> Tag:
    return Tag(id=id, user_id=user_id, name=name)


@pytest.fixture
def mock_storage():
    storage = MagicMock()
    storage.get_download_url.return_value = "https://oss.example.com/uploads/test.mp4?sign=xxx"
    return storage


@pytest.fixture
def client(mock_storage):
    """创建带有依赖覆盖的 TestClient。"""
    test_app = FastAPI()
    test_app.include_router(router, prefix="/api/v1/assets")

    project = _make_project()
    library = _make_library()
    asset_repo = StubAssetRepository()
    project_repo = StubProjectRepository({project.id: project})
    library_repo = StubAssetLibraryRepository({library.id: library})
    tag_repo = StubTagRepository()

    def _override_current_user():
        mock_auth = MagicMock(spec=AuthenticatedUser)
        mock_auth.user = _make_user()
        return mock_auth

    test_app.dependency_overrides[get_current_user] = _override_current_user
    test_app.dependency_overrides[get_project_repository] = lambda: project_repo
    test_app.dependency_overrides[get_asset_library_repository] = lambda: library_repo
    test_app.dependency_overrides[get_asset_repository] = lambda: asset_repo
    test_app.dependency_overrides[get_tag_repository] = lambda: tag_repo
    test_app.dependency_overrides[get_storage_service] = lambda: mock_storage

    yield TestClient(test_app)

    test_app.dependency_overrides.clear()


# ---------------------------------------------------------------------------
# 3. POST /assets — 创建素材
# ---------------------------------------------------------------------------


class TestCreateAsset:
    """创建素材端点测试。"""

    def test_create_asset_returns_410_gone(self, client):
        """create_asset 已废弃，返回 410 Gone 提示使用 ingest-jobs。"""
        resp = client.post(
            "/api/v1/assets",
            json={
                "project_id": "proj-1",
                "library_id": "lib-1",
                "name": "new-video.mp4",
                "storage_key": "uploads/new-video.mp4",
                "mime_type": "video/mp4",
            },
        )
        assert resp.status_code == 410

    def test_create_asset_any_type_returns_410(self, client):
        """所有类型都返回 410 Gone（图片/音频也废弃）。"""
        resp = client.post(
            "/api/v1/assets",
            json={
                "project_id": "proj-1",
                "library_id": "lib-1",
                "name": "photo.jpg",
                "storage_key": "uploads/photo.jpg",
                "mime_type": "image/jpeg",
            },
        )
        assert resp.status_code == 410


# ---------------------------------------------------------------------------
# 4. GET /assets — 获取素材列表
# ---------------------------------------------------------------------------


class TestListAssets:
    """获取素材列表端点测试。"""

    def _create_test_assets(self, client, count: int = 3):
        """辅助方法：直接插入测试素材到 repository（绕过已废弃的 create_asset API）。"""
        # 通过依赖覆盖获取 asset_repo
        app = client.app
        asset_repo = app.dependency_overrides.get(get_asset_repository, lambda: None)()
        if asset_repo is None:
            return
        for i in range(count):
            import uuid

            asset = Asset(
                id=uuid.uuid4().hex,
                project_id="proj-1",
                library_id="lib-1",
                name=f"video-{i}.mp4",
                storage_key=f"uploads/video-{i}.mp4",
                mime_type="video/mp4",
                file_size=1024 * (i + 1),
                status=AssetStatus.READY,
            )
            asset_repo.create(asset)

    def test_empty_list(self, client):
        """无素材时返回空列表。"""
        resp = client.get("/api/v1/assets?library_id=lib-1")
        assert resp.status_code == 200
        data = resp.json()
        assert data["items"] == []
        assert data["total"] == 0

    def test_list_assets_by_library(self, client):
        """按素材库列出素材。"""
        self._create_test_assets(client, 3)

        resp = client.get("/api/v1/assets?library_id=lib-1")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["items"]) == 3
        assert data["total"] >= 3

    def test_list_assets_by_project(self, client):
        """按项目列出素材。"""
        self._create_test_assets(client, 2)

        resp = client.get("/api/v1/assets?project_id=proj-1")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["items"]) == 2

    def test_list_pagination(self, client):
        """分页参数生效。"""
        self._create_test_assets(client, 5)

        resp = client.get("/api/v1/assets?library_id=lib-1&skip=0&limit=2")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["items"]) == 2
        assert data["skip"] == 0
        assert data["limit"] == 2

    def test_list_page_page_size(self, client):
        """page/page_size 分页参数生效。"""
        self._create_test_assets(client, 5)

        resp = client.get("/api/v1/assets?library_id=lib-1&page=1&page_size=2")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["items"]) == 2
        assert data["skip"] == 0
        assert data["limit"] == 2

        resp = client.get("/api/v1/assets?library_id=lib-1&page=3&page_size=2")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["items"]) == 1  # 第3页只有1条

    def test_list_status_filter_default_excludes_deleted(self, client):
        """默认列表排除deleted状态素材（批量删除走软删除）。"""
        self._create_test_assets(client, 3)

        # 批量删除2个
        items = client.get("/api/v1/assets?library_id=lib-1").json()["items"]
        delete_ids = [items[0]["id"], items[1]["id"]]
        client.post("/api/v1/assets/batch-delete", json={"asset_ids": delete_ids})

        # 默认查询应该只剩1个（排除deleted）
        resp = client.get("/api/v1/assets?library_id=lib-1")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["items"]) == 1
        assert data["total"] == 1

        # status=deleted 查回收站
        resp = client.get("/api/v1/assets?library_id=lib-1&status=deleted")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["items"]) == 2

        # status=all 查全部
        resp = client.get("/api/v1/assets?library_id=lib-1&status=all")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["items"]) == 3

    def test_list_status_filter_uploading_visible(self, client):
        """uploading状态的素材默认能看到（上传后立即显示处理中）。"""
        _direct_insert_asset(client, name="uploading-test.mp4", status=AssetStatus.UPLOADING)

        resp = client.get("/api/v1/assets?library_id=lib-1")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["items"]) == 1
        assert data["items"][0]["status"] == "uploading"

    def test_list_with_keyword_filter(self, client):
        """按名称关键词过滤。"""
        _direct_insert_asset(client, name="hello-world.mp4")
        _direct_insert_asset(client, name="goodbye.mp4", mime_type="video/mp4", status=AssetStatus.READY)

        resp = client.get("/api/v1/assets?library_id=lib-1&keyword=hello")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["items"]) == 1
        assert "hello" in data["items"][0]["name"].lower()


# ---------------------------------------------------------------------------
# 5. GET /assets/{asset_id} — 获取单个素材详情
# ---------------------------------------------------------------------------


class TestGetAsset:
    """获取单个素材详情端点测试。"""

    def _create_asset(self, client) -> str:
        """Direct insert into repo (create_asset API is deprecated/410)."""
        return _direct_insert_asset(client)

    def test_get_asset_success(self, client):
        """获取存在的素材详情成功。"""
        asset_id = self._create_asset(client)

        resp = client.get(f"/api/v1/assets/{asset_id}")
        assert resp.status_code == 200
        data = resp.json()
        assert data["id"] == asset_id
        assert data["name"] == "test-video.mp4"
        assert "file_url" in data
        assert "status" in data

    def test_get_nonexistent_asset_returns_404(self, client):
        """获取不存在的素材返回 404。"""
        resp = client.get("/api/v1/assets/nonexistent-asset-id")
        assert resp.status_code == 404
        assert "not found" in resp.json()["detail"].lower() or "Asset" in resp.json()["detail"]


# ---------------------------------------------------------------------------
# 6. PUT /assets/{asset_id} — 更新素材
# ---------------------------------------------------------------------------


class TestUpdateAsset:
    """更新素材端点测试。"""

    def _create_asset(self, client) -> str:
        """Direct insert into repo (create_asset API is deprecated/410)."""
        return _direct_insert_asset(client)

    def test_update_asset_name(self, client):
        """更新素材名称成功。"""
        asset_id = self._create_asset(client)

        resp = client.put(
            f"/api/v1/assets/{asset_id}",
            json={"name": "new-name.mp4"},
        )
        assert resp.status_code == 200
        assert resp.json()["name"] == "new-name.mp4"

    def test_update_asset_metadata(self, client):
        """更新素材 metadata 成功。"""
        asset_id = self._create_asset(client)

        resp = client.put(
            f"/api/v1/assets/{asset_id}",
            json={"metadata": {"description": "这是一段测试视频", "category": "demo"}},
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["metadata"]["description"] == "这是一段测试视频"
        assert data["metadata"]["category"] == "demo"

    def test_update_nonexistent_asset_returns_404(self, client):
        """更新不存在的素材返回 404。"""
        resp = client.put(
            "/api/v1/assets/nonexistent-id",
            json={"name": "test.mp4"},
        )
        assert resp.status_code == 404

    def test_update_with_empty_body(self, client):
        """空请求体也应返回成功（不修改任何字段）。"""
        asset_id = self._create_asset(client)

        resp = client.put(f"/api/v1/assets/{asset_id}", json={})
        assert resp.status_code == 200
        assert resp.json()["name"] == "test-video.mp4"


# ---------------------------------------------------------------------------
# 7. DELETE /assets/{asset_id} — 删除素材
# ---------------------------------------------------------------------------


class TestDeleteAsset:
    """删除素材端点测试。"""

    def _create_asset(self, client) -> str:
        """Direct insert into repo (create_asset API is deprecated/410)."""
        return _direct_insert_asset(client)

    def test_delete_asset_success(self, client):
        """删除存在的素材成功，返回 204。"""
        asset_id = self._create_asset(client)

        resp = client.delete(f"/api/v1/assets/{asset_id}")
        assert resp.status_code == 204

        # 验证已删除
        get_resp = client.get(f"/api/v1/assets/{asset_id}")
        assert get_resp.status_code == 404

    def test_delete_nonexistent_asset_returns_404(self, client):
        """删除不存在的素材返回 404。"""
        resp = client.delete("/api/v1/assets/nonexistent-id")
        assert resp.status_code == 404

    def test_delete_idempotent(self, client):
        """删除后再次删除返回 404（幂等性）。"""
        asset_id = self._create_asset(client)

        resp1 = client.delete(f"/api/v1/assets/{asset_id}")
        assert resp1.status_code == 204

        resp2 = client.delete(f"/api/v1/assets/{asset_id}")
        assert resp2.status_code == 404


# ---------------------------------------------------------------------------
# 8. POST /assets/batch-delete — 批量删除素材
# ---------------------------------------------------------------------------


class TestBatchDeleteAssets:
    """批量删除素材端点测试。"""

    def _create_assets(self, client, count: int = 3) -> list[str]:
        ids = []
        for i in range(count):
            aid = _direct_insert_asset(client, name=f"batch-{i}.mp4")
            ids.append(aid)
        return ids

    def test_batch_delete_success(self, client):
        """批量删除成功（软删除）。"""
        ids = self._create_assets(client, 3)

        resp = client.post(
            "/api/v1/assets/batch-delete",
            json={"asset_ids": ids[:2]},
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["success_count"] == 2
        assert len(data["failed_ids"]) == 0
        # 软删除：记录仍在，status 变为 deleted
        for aid in ids[:2]:
            r = client.get(f"/api/v1/assets/{aid}")
            assert r.status_code == 200
            assert r.json()["status"] == "deleted"

    def test_batch_delete_with_nonexistent_ids(self, client):
        """批量删除包含不存在的 ID，失败的计入 failed_ids。"""
        ids = self._create_assets(client, 2)
        ids.append("nonexistent-id")

        resp = client.post(
            "/api/v1/assets/batch-delete",
            json={"asset_ids": ids},
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["success_count"] == 2
        assert "nonexistent-id" in data["failed_ids"]
        assert "nonexistent-id" in data["failed_details"]
        assert data["failed_details"]["nonexistent-id"] == "not_found"

    def test_batch_delete_empty_list_returns_422(self, client):
        """空列表返回 422。"""
        resp = client.post(
            "/api/v1/assets/batch-delete",
            json={"asset_ids": []},
        )
        assert resp.status_code == 422


# ---------------------------------------------------------------------------
# 9. 标签相关测试
# ---------------------------------------------------------------------------


class TestAssetTags:
    """素材标签相关端点测试。"""

    def _create_asset(self, client) -> str:
        """Direct insert into repo (create_asset API is deprecated/410)."""
        return _direct_insert_asset(client)

    def test_add_tags_to_asset(self, client):
        """给素材打标签。需要先在 tag_repo 中创建标签。"""
        # 由于 tag_repo 在 fixture 内部创建，我们通过另一种方式测试
        # 直接测试不存在的标签返回 404
        asset_id = self._create_asset(client)

        resp = client.post(
            f"/api/v1/assets/{asset_id}/tags",
            json={"tag_ids": ["nonexistent-tag"]},
        )
        # 标签不存在应返回 404
        assert resp.status_code == 404
        assert "Tag" in resp.json()["detail"]

    def test_remove_tag_from_asset(self, client):
        """移除素材标签（幂等，不存在也返回 204）。"""
        asset_id = self._create_asset(client)

        resp = client.delete(f"/api/v1/assets/{asset_id}/tags/nonexistent-tag")
        # 移除标签是幂等的，标签不存在也应返回 204
        assert resp.status_code == 204


# ---------------------------------------------------------------------------
# 10. 跨端点集成场景
# ---------------------------------------------------------------------------


class TestAssetsCRUDFlow:
    """素材完整 CRUD 流程测试。"""

    def test_full_crud_flow(self, client):
        """测试完整的创建 → 列表 → 详情 → 更新 → 删除流程。"""
        # 1. 创建 (direct insert since create_asset is 410)
        asset_id = _direct_insert_asset(client, name="crud-flow.mp4")

        # 2. 列表中应包含
        list_resp = client.get("/api/v1/assets?library_id=lib-1")
        assert list_resp.status_code == 200
        assert any(item["id"] == asset_id for item in list_resp.json()["items"])

        # 3. 获取详情
        detail_resp = client.get(f"/api/v1/assets/{asset_id}")
        assert detail_resp.status_code == 200
        assert detail_resp.json()["name"] == "crud-flow.mp4"

        # 4. 更新名称
        update_resp = client.put(
            f"/api/v1/assets/{asset_id}",
            json={"name": "crud-flow-updated.mp4"},
        )
        assert update_resp.status_code == 200
        assert update_resp.json()["name"] == "crud-flow-updated.mp4"

        # 5. 验证更新生效
        detail_resp2 = client.get(f"/api/v1/assets/{asset_id}")
        assert detail_resp2.json()["name"] == "crud-flow-updated.mp4"

        # 6. 删除
        delete_resp = client.delete(f"/api/v1/assets/{asset_id}")
        assert delete_resp.status_code == 204

        # 7. 验证已删除
        detail_resp3 = client.get(f"/api/v1/assets/{asset_id}")
        assert detail_resp3.status_code == 404


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
