"""#1894 方向修正 — 集成测试. 覆盖: - ScriptModel 不再有 title_text/title_category/title_config 列 - ScriptService 不再接受/暴露这三个字段 - /api/v1/titles/* 所有方法返回 410 Gone - /api/v1/scripts 响应不含这三个字段 """ from __future__ import annotations import os import sys import uuid from datetime import UTC, datetime, timezone from pathlib import Path from unittest.mock import MagicMock, patch import pytest from fastapi.testclient import TestClient _APPS_API = str(Path(__file__).resolve().parents[2] / "apps" / "api") if _APPS_API not in sys.path: sys.path.insert(0, _APPS_API) os.environ.setdefault("JWT_SECRET_KEY", "dev-secret-key-for-testing") from main import app # noqa: E402 def _make_mock_auth_user(user_id="user-001"): return MagicMock(user=MagicMock(id=user_id)) # ── TestScriptModelNoTitleFields ───────────────────────────────────────── class TestScriptModelNoTitleFields: """验证 ScriptModel 已删除 title_text/title_category/title_config 列.""" def test_model_has_no_title_text(self): from packages.adapters.sqlalchemy_impl.models import ScriptModel assert not hasattr(ScriptModel, "title_text") or "title_text" not in { c.name for c in ScriptModel.__table__.columns } def test_model_has_no_title_category(self): from packages.adapters.sqlalchemy_impl.models import ScriptModel assert "title_category" not in {c.name for c in ScriptModel.__table__.columns} def test_model_has_no_title_config(self): from packages.adapters.sqlalchemy_impl.models import ScriptModel assert "title_config" not in {c.name for c in ScriptModel.__table__.columns} def test_model_retains_core_fields(self): from packages.adapters.sqlalchemy_impl.models import ScriptModel cols = {c.name for c in ScriptModel.__table__.columns} for expected in ("id", "user_id", "title", "content", "segments", "tags", "created_at", "updated_at"): assert expected in cols, f"ScriptModel 缺字段 {expected}" # ── TestScriptServiceSignature ────────────────────────────────────────── class TestScriptServiceSignature: """验证 ScriptService 的 create/update 不接受已删除字段.""" def test_create_script_rejects_title_fields(self): """Python 层:传入旧字段应抛 TypeError(被移除了).""" import inspect from app.services.script_service import ScriptService sig = inspect.signature(ScriptService.create_script) for name in ("title_text", "title_category", "title_config"): assert name not in sig.parameters, f"create_script 仍接受参数 {name}" def test_update_script_rejects_title_fields(self): import inspect from app.services.script_service import ScriptService sig = inspect.signature(ScriptService.update_script) for name in ("title_text", "title_category", "title_config"): assert name not in sig.parameters, f"update_script 仍接受参数 {name}" def test_get_title_config_removed(self): from app.services.script_service import ScriptService assert not hasattr(ScriptService, "get_title_config_for_script"), "get_title_config_for_script 应已删除" # ── TestTitlesApiGone ─────────────────────────────────────────────────── class TestTitlesApiGone: """验证 /api/v1/titles 所有方法返回 410 Gone.""" def setup_method(self): from app.auth import get_current_user self._mock_user = _make_mock_auth_user() app.dependency_overrides[get_current_user] = lambda: self._mock_user self.client = TestClient(app) def teardown_method(self): app.dependency_overrides.clear() @pytest.mark.parametrize( "method,path", [ ("get", "/api/v1/titles"), ("post", "/api/v1/titles"), ("get", "/api/v1/titles/some-id"), ("put", "/api/v1/titles/some-id"), ("delete", "/api/v1/titles/some-id"), ("patch", "/api/v1/titles/some-id"), ("get", "/api/v1/titles/any/nested/path"), ], ) def test_titles_routes_return_410(self, method, path): resp = getattr(self.client, method)(path) assert resp.status_code == 410, f"{method.upper()} {path} 应返回 410,实际 {resp.status_code}: {resp.text}" data = resp.json() assert "error" in data or "message" in data or "GONE" in resp.text # Deprecation header assert resp.headers.get("Deprecation") == "true"