mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
refactor: isolate media server and site userdata queries
This commit is contained in:
+3
-63
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"model_decorators": {
|
||||
"by_kind": {
|
||||
"async_db_query": 14,
|
||||
"async_db_query": 9,
|
||||
"async_db_update": 0,
|
||||
"db_query": 16,
|
||||
"db_query": 9,
|
||||
"db_update": 0
|
||||
},
|
||||
"count": 30,
|
||||
"count": 18,
|
||||
"methods": [
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
@@ -38,41 +38,6 @@
|
||||
"file": "app/db/models/agenttaskrun.py",
|
||||
"method": "AgentTaskRun.list_for_task"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.async_exist_by_media_identity"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.async_exists_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.async_get_by_itemid"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.exist_by_media_identity"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.exists_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.get_by_itemid"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.get_by_server_itemid"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/passkey.py",
|
||||
@@ -93,31 +58,6 @@
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.get_by_id"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.async_get_by_domain"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.async_get_latest"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.get_by_date"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.get_by_domain"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.get_latest"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribehistory.py",
|
||||
|
||||
@@ -126,8 +126,8 @@ def test_transaction_debt_baseline_is_a_model_and_oper_ratchet() -> None:
|
||||
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
|
||||
|
||||
assert baseline["schema_version"] == 1
|
||||
assert baseline["model_decorators"]["count"] == 30
|
||||
assert sum(baseline["model_decorators"]["by_kind"].values()) == 30
|
||||
assert baseline["model_decorators"]["count"] == 18
|
||||
assert sum(baseline["model_decorators"]["by_kind"].values()) == 18
|
||||
assert baseline["model_decorators"]["by_kind"]["db_update"] == 0
|
||||
assert baseline["model_decorators"]["by_kind"]["async_db_update"] == 0
|
||||
assert baseline["model_transaction_calls"] == {"count": 0, "calls": []}
|
||||
|
||||
@@ -9,7 +9,10 @@ import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from app.db import decorators
|
||||
from app.db.models.mediaserver import MediaServerItem
|
||||
from app.db.oper.mediaserver import MediaServerOper
|
||||
from app.db.session import SessionFactory, async_session_scope
|
||||
from app.schemas.types import MediaSource
|
||||
|
||||
|
||||
@@ -40,6 +43,52 @@ def test_get_by_itemid_matches_async_twin(db):
|
||||
assert MediaServerItem.get_by_itemid(db.session, "it-missing") is None
|
||||
|
||||
|
||||
def test_mediaserver_oper_reuses_explicit_query_sessions(db, monkeypatch):
|
||||
"""媒体服务器 Oper 绑定调用方会话后不得再创建兼容查询会话。"""
|
||||
db.add(_item("emby", "explicit-ms", media_id="explicit-1001"))
|
||||
monkeypatch.setattr(
|
||||
decorators,
|
||||
"ScopedSession",
|
||||
lambda: (_ for _ in ()).throw(AssertionError("不应创建额外同步会话")),
|
||||
)
|
||||
|
||||
assert MediaServerOper(db.session).exists(
|
||||
media_source=MediaSource.TMDB,
|
||||
media_id="explicit-1001",
|
||||
mtype="电影",
|
||||
) is not None
|
||||
|
||||
async def check() -> None:
|
||||
"""验证异步存在性查询复用显式 AsyncSession。"""
|
||||
async with async_session_scope() as session:
|
||||
monkeypatch.setattr(
|
||||
decorators,
|
||||
"async_session_scope",
|
||||
lambda: (_ for _ in ()).throw(AssertionError("不应创建额外异步会话")),
|
||||
)
|
||||
assert await MediaServerOper(session).async_exists(
|
||||
media_source=MediaSource.TMDB,
|
||||
media_id="explicit-1001",
|
||||
mtype="电影",
|
||||
) is not None
|
||||
|
||||
asyncio.run(check())
|
||||
|
||||
|
||||
def test_mediaserver_model_legacy_query_keeps_keyword_abi(db, monkeypatch):
|
||||
"""旧插件以关键字直调媒体服务器 Model 时仍自动补入短会话。"""
|
||||
db.add(_item("emby", "legacy-ms"))
|
||||
opened = []
|
||||
monkeypatch.setattr(
|
||||
decorators,
|
||||
"ScopedSession",
|
||||
lambda: (opened.append(True) or SessionFactory()),
|
||||
)
|
||||
|
||||
assert MediaServerItem.get_by_itemid(item_id="legacy-ms") is not None
|
||||
assert opened == [True]
|
||||
|
||||
|
||||
def test_get_by_server_itemid_scopes_by_server(db):
|
||||
"""
|
||||
条目 ID 只在单个服务器内唯一,查找必须同时限定服务器。
|
||||
|
||||
@@ -9,10 +9,13 @@ import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from app.db import decorators
|
||||
from app.db.models.site import Site
|
||||
from app.db.models.siteicon import SiteIcon
|
||||
from app.db.models.sitestatistic import SiteStatistic
|
||||
from app.db.models.siteuserdata import SiteUserData
|
||||
from app.db.oper.site import SiteOper
|
||||
from app.db.session import SessionFactory, async_session_scope
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -187,6 +190,46 @@ def test_userdata_get_by_domain_matches_async_twin(db):
|
||||
assert len(sync_rows) == len(async_rows)
|
||||
|
||||
|
||||
def test_site_oper_reuses_explicit_userdata_query_sessions(db, monkeypatch):
|
||||
"""站点用户数据 Oper 必须复用调用方同步与异步会话。"""
|
||||
db.add(_userdata("explicit-site.test", "2026-08-12", "10:00:00"))
|
||||
monkeypatch.setattr(
|
||||
decorators,
|
||||
"ScopedSession",
|
||||
lambda: (_ for _ in ()).throw(AssertionError("不应创建额外同步会话")),
|
||||
)
|
||||
|
||||
assert SiteOper(db.session).get_userdata_by_domain("explicit-site.test")
|
||||
|
||||
async def check() -> None:
|
||||
"""验证异步站点用户数据查询复用显式 AsyncSession。"""
|
||||
async with async_session_scope() as session:
|
||||
monkeypatch.setattr(
|
||||
decorators,
|
||||
"async_session_scope",
|
||||
lambda: (_ for _ in ()).throw(AssertionError("不应创建额外异步会话")),
|
||||
)
|
||||
assert await SiteOper(session).async_get_userdata_by_domain(
|
||||
"explicit-site.test"
|
||||
)
|
||||
|
||||
asyncio.run(check())
|
||||
|
||||
|
||||
def test_site_userdata_model_legacy_query_keeps_keyword_abi(db, monkeypatch):
|
||||
"""旧插件以关键字直调 SiteUserData 时仍自动补入短会话。"""
|
||||
db.add(_userdata("legacy-site.test", "2026-08-12", "10:00:00"))
|
||||
opened = []
|
||||
monkeypatch.setattr(
|
||||
decorators,
|
||||
"ScopedSession",
|
||||
lambda: (opened.append(True) or SessionFactory()),
|
||||
)
|
||||
|
||||
assert SiteUserData.get_by_domain(domain="legacy-site.test")
|
||||
assert opened == [True]
|
||||
|
||||
|
||||
def test_userdata_get_by_date_returns_all_domains_of_that_day(db):
|
||||
"""
|
||||
按日期查询应跨站点返回当天全部快照。
|
||||
|
||||
Reference in New Issue
Block a user