Files
MoviePilot/tests/test_plugin_instance_default_target_oper.py
Aqr-K d44480d739 Merge pull request #6666 from Aqr-K/feat/plugin-instance-target-enablement
feat(plugin): 插件实例默认调用目标与启停,未指定实例时不再兜底取第一个
2026-09-13 16:37:27 +08:00

136 lines
4.6 KiB
Python

"""插件实例默认调用目标置位与清除的数据访问层测试。"""
from __future__ import annotations
import pytest
from sqlalchemy import delete
from sqlalchemy.orm import Session
from app.db.models.plugininstance import PluginInstance
from app.db.oper.plugininstance import PluginInstanceOper
from app.db.uow import run_sync_transaction
_INSTANCE_IDS = (
"TargetOperHost",
"TargetOperHostX2",
"TargetOperHostX3",
"TargetOperOther",
)
@pytest.fixture(autouse=True)
def purge_written_rows():
"""删除用例写入的行,测试库在整个会话内共享,残留会干扰后续用例。"""
yield
def purge(session: Session) -> None:
"""按本文件使用的固定标识精确删除,不触碰其他用例的数据。"""
session.execute(
delete(PluginInstance).where(
PluginInstance.instance_id.in_(_INSTANCE_IDS)
)
)
run_sync_transaction(purge)
def _seed(oper: PluginInstanceOper) -> None:
"""建出一个本体和两个分身,供置位与清除用例共用。"""
oper.save(instance_id="TargetOperHost", source_plugin_id="TargetOperHost")
oper.save(instance_id="TargetOperHostX2", source_plugin_id="TargetOperHost")
oper.save(instance_id="TargetOperHostX3", source_plugin_id="TargetOperHost")
def test_set_default_target_marks_only_the_requested_row():
"""置位只落在目标行上,同插件其余行保持未置位。"""
oper = PluginInstanceOper()
_seed(oper)
assert oper.set_default_target("TargetOperHost", "TargetOperHostX2") is True
flags = {
record.instance_id: record.is_default_target
for record in oper.list_by_source("TargetOperHost")
}
assert flags == {
"TargetOperHost": False,
"TargetOperHostX2": True,
"TargetOperHostX3": False,
}
def test_set_default_target_clears_the_previous_default_in_one_transaction():
"""改置默认目标时旧置位必须一并清掉,不能出现两行同时为真。
「同一源插件至多一个默认目标」由条件唯一索引在库层强制,清旧与置新若分处两个
事务就会撞上索引;这条用例正是在真实连接上证明清旧置新落在同一次写入里。
"""
oper = PluginInstanceOper()
_seed(oper)
oper.set_default_target("TargetOperHost", "TargetOperHostX2")
assert oper.set_default_target("TargetOperHost", "TargetOperHostX3") is True
marked = [
record.instance_id
for record in oper.list_by_source("TargetOperHost")
if record.is_default_target
]
assert marked == ["TargetOperHostX3"]
def test_set_default_target_rejects_a_row_belonging_to_another_plugin():
"""目标行不归属该源插件时原样返回失败,且不动原有置位。"""
oper = PluginInstanceOper()
_seed(oper)
oper.save(instance_id="TargetOperOther", source_plugin_id="TargetOperOther")
oper.set_default_target("TargetOperHost", "TargetOperHostX2")
assert oper.set_default_target("TargetOperHost", "TargetOperOther") is False
marked = [
record.instance_id
for record in oper.list_by_source("TargetOperHost")
if record.is_default_target
]
assert marked == ["TargetOperHostX2"]
def test_set_default_target_reports_false_for_a_row_that_does_not_exist():
"""目标行尚未落盘时如实返回失败,不隐式建行。"""
oper = PluginInstanceOper()
_seed(oper)
assert oper.set_default_target("TargetOperHost", "TargetOperHostMissing") is False
assert oper.get("TargetOperHostMissing") is None
def test_clear_default_target_is_idempotent():
"""清除置位后重复调用保持幂等,不报错也不产生新的置位。"""
oper = PluginInstanceOper()
_seed(oper)
oper.set_default_target("TargetOperHost", "TargetOperHostX2")
oper.clear_default_target("TargetOperHost")
oper.clear_default_target("TargetOperHost")
assert not any(
record.is_default_target for record in oper.list_by_source("TargetOperHost")
)
def test_host_row_carrying_only_a_default_target_is_not_recycled_as_empty():
"""本体行只剩默认目标置位时不算「只剩身份列」,不能被空行回收清掉。
回收判据漏掉这一列会让用户把本体设为默认调用目标后,下一次清空配置就把置位
连同整行一起删掉,默认目标无声失效。
"""
oper = PluginInstanceOper()
_seed(oper)
oper.set_default_target("TargetOperHost", "TargetOperHost")
record = oper.get("TargetOperHost")
assert record is not None
assert record.is_default_target is True
assert record.carries_only_identity is False