mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 16:07:01 +08:00
fix(db): 假值系统配置落库而非删除记录,布尔开关关闭态可持久化(#6309)
SystemConfigOper.set/async_set 原先对假值(False/0/None/空容器)执行删除记录, 读取端以「无记录」表示未配置并回落默认值,导致「挂载盘删除空目录」等布尔开关 关闭后刷新自动弹回开启。现改为假值同样落库更新,显式删除仍走 delete 方法。
This commit is contained in:
@@ -41,10 +41,9 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
|
|||||||
conf = SystemConfig.get_by_key(self._db, key)
|
conf = SystemConfig.get_by_key(self._db, key)
|
||||||
if conf:
|
if conf:
|
||||||
if old_value != value:
|
if old_value != value:
|
||||||
if value:
|
# 假值(False/0/None/空容器)同样落库而不是删除记录:
|
||||||
conf.update(self._db, {"value": value})
|
# 读取端以「无记录」表示未配置并回落默认值,删除会使布尔开关的关闭态无法持久化
|
||||||
else:
|
conf.update(self._db, {"value": value})
|
||||||
conf.delete(self._db, conf.id)
|
|
||||||
return True
|
return True
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
@@ -77,10 +76,9 @@ class SystemConfigOper(DbOper, metaclass=Singleton):
|
|||||||
return None
|
return None
|
||||||
# 执行数据库更新
|
# 执行数据库更新
|
||||||
if conf:
|
if conf:
|
||||||
if value:
|
# 假值(False/0/None/空容器)同样落库而不是删除记录:
|
||||||
await conf.async_update(self._db, {"value": value})
|
# 读取端以「无记录」表示未配置并回落默认值,删除会使布尔开关的关闭态无法持久化
|
||||||
else:
|
await conf.async_update(self._db, {"value": value})
|
||||||
await conf.async_delete(self._db, conf.id)
|
|
||||||
else:
|
else:
|
||||||
conf = SystemConfig(key=key, value=value)
|
conf = SystemConfig(key=key, value=value)
|
||||||
await conf.async_create(self._db)
|
await conf.async_create(self._db)
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
import threading
|
import threading
|
||||||
|
import uuid
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.db.models.systemconfig import SystemConfig
|
||||||
from app.db.systemconfig_oper import SystemConfigOper
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
from app.schemas.types import SystemConfigKey
|
from app.schemas.types import SystemConfigKey
|
||||||
|
from app.utils.singleton import Singleton
|
||||||
|
|
||||||
|
|
||||||
|
def _unique_key() -> str:
|
||||||
|
"""生成独立测试键,避免用例间相互污染。"""
|
||||||
|
return f"__test__{uuid.uuid4().hex}"
|
||||||
|
|
||||||
|
|
||||||
|
def _fresh_oper() -> SystemConfigOper:
|
||||||
|
"""重置单例并从数据库重新加载配置缓存。"""
|
||||||
|
Singleton._instances.pop((SystemConfigOper, (), frozenset()), None)
|
||||||
|
return SystemConfigOper()
|
||||||
|
|
||||||
|
|
||||||
def test_increment_serializes_concurrent_counter_updates(monkeypatch):
|
def test_increment_serializes_concurrent_counter_updates(monkeypatch):
|
||||||
@@ -49,3 +65,76 @@ def test_increment_supports_custom_step(monkeypatch):
|
|||||||
|
|
||||||
assert result == 7
|
assert result == 7
|
||||||
assert stored_value["value"] == 7
|
assert stored_value["value"] == 7
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_creates_record_for_falsy_value():
|
||||||
|
"""无记录时写入假值应创建记录,而不是丢弃配置。"""
|
||||||
|
key = _unique_key()
|
||||||
|
oper = _fresh_oper()
|
||||||
|
|
||||||
|
assert oper.set(key, False) is True
|
||||||
|
assert oper.get(key) is False
|
||||||
|
assert SystemConfig.get_by_key(oper._db, key) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_persists_falsy_value_on_existing_record():
|
||||||
|
"""已存在记录时写入假值应更新记录,而不是删除记录回落默认值。"""
|
||||||
|
key = _unique_key()
|
||||||
|
oper = _fresh_oper()
|
||||||
|
|
||||||
|
oper.set(key, True)
|
||||||
|
assert oper.set(key, False) is True
|
||||||
|
assert oper.get(key) is False
|
||||||
|
assert SystemConfig.get_by_key(oper._db, key).value is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_async_set_persists_falsy_value_on_existing_record():
|
||||||
|
"""异步写入假值同样应更新记录而不是删除记录。"""
|
||||||
|
key = _unique_key()
|
||||||
|
oper = _fresh_oper()
|
||||||
|
|
||||||
|
oper.set(key, True)
|
||||||
|
assert await oper.async_set(key, False) is True
|
||||||
|
assert oper.get(key) is False
|
||||||
|
assert SystemConfig.get_by_key(oper._db, key).value is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_async_set_creates_record_for_falsy_value():
|
||||||
|
"""异步写入假值且无记录时应创建记录。"""
|
||||||
|
key = _unique_key()
|
||||||
|
oper = _fresh_oper()
|
||||||
|
|
||||||
|
assert await oper.async_set(key, 0) is True
|
||||||
|
assert oper.get(key) == 0
|
||||||
|
assert SystemConfig.get_by_key(oper._db, key).value == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_removes_record_explicitly():
|
||||||
|
"""显式 delete 仍是删除配置的唯一途径。"""
|
||||||
|
key = _unique_key()
|
||||||
|
oper = _fresh_oper()
|
||||||
|
|
||||||
|
oper.set(key, False)
|
||||||
|
assert oper.delete(key) is True
|
||||||
|
assert oper.get(key) is None
|
||||||
|
assert SystemConfig.get_by_key(oper._db, key) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_mounted_local_disk_delete_empty_dirs_off_is_persisted():
|
||||||
|
"""回归:挂载盘删除空目录开关关闭后,读取端不应回落默认开启(issue #6309)。"""
|
||||||
|
key = SystemConfigKey.MountedLocalDiskDeleteEmptyDirs.value
|
||||||
|
oper = _fresh_oper()
|
||||||
|
|
||||||
|
# 模拟前端保存关闭:已有记录(开启)→ 关闭
|
||||||
|
oper.set(key, True)
|
||||||
|
oper.set(key, False)
|
||||||
|
# 复刻 app/chain/transfer.py 的读取语义:无记录视为默认开启
|
||||||
|
assert (oper.get(key) is not False) is False
|
||||||
|
|
||||||
|
# 模拟全新安装首次保存关闭:无记录 → 创建关闭记录
|
||||||
|
Singleton._instances.pop((SystemConfigOper, (), frozenset()), None)
|
||||||
|
oper.delete(key)
|
||||||
|
_fresh_oper().set(key, False)
|
||||||
|
assert _fresh_oper().get(key) is False
|
||||||
|
|||||||
Reference in New Issue
Block a user