You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buffer_platform/tests/test_config_store.py

198 lines
7.0 KiB

"""配置存储测试MySQL + JSON fallback。"""
import json
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from app.config_store import ConfigStore
def _make_session_maker(mock_db):
"""构造正确的 sessionmaker mock使 with 语句返回 mock_db。"""
session_obj = MagicMock()
session_obj.__enter__ = MagicMock(return_value=mock_db)
session_obj.__exit__ = MagicMock(return_value=False)
session_maker = MagicMock(return_value=session_obj)
return session_maker
class TestConfigStoreGet:
"""ConfigStore.get_config 测试。"""
def test_get_config_returns_mysql_value_on_hit(self, tmp_path):
"""MySQL 命中时返回数据库中的配置。"""
mock_config = MagicMock()
mock_config.config_value = {"futures": {"沪银": "AG2608"}}
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = mock_config
storage = MagicMock()
storage.check_mysql.return_value = True
store = ConfigStore(
storage_manager=storage,
config_dir=tmp_path,
session_maker=_make_session_maker(mock_db),
)
result = store.get_config("symbols", {"futures": {}})
assert result == {"futures": {"沪银": "AG2608"}}
def test_get_config_reads_json_and_backfills_when_mysql_miss(self, tmp_path):
"""MySQL 未命中时读取 JSON 并回填数据库。"""
json_path = tmp_path / "symbols_config.json"
json_path.write_text(json.dumps({"futures": {"沪银": "AG2608"}}, ensure_ascii=False), encoding="utf-8")
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
storage = MagicMock()
storage.check_mysql.return_value = True
store = ConfigStore(
storage_manager=storage,
config_dir=tmp_path,
session_maker=_make_session_maker(mock_db),
)
result = store.get_config("symbols", {"futures": {}})
assert result == {"futures": {"沪银": "AG2608"}}
mock_db.add.assert_called_once()
mock_db.commit.assert_called_once()
def test_get_config_falls_back_to_json_when_mysql_unavailable(self, tmp_path):
"""MySQL 不可用时直接读取 JSON。"""
json_path = tmp_path / "symbols_config.json"
json_path.write_text(json.dumps({"futures": {"沪银": "AG2608"}}, ensure_ascii=False), encoding="utf-8")
storage = MagicMock()
storage.check_mysql.return_value = False
store = ConfigStore(
storage_manager=storage,
config_dir=tmp_path,
session_maker=None,
)
result = store.get_config("symbols", {"futures": {}})
assert result == {"futures": {"沪银": "AG2608"}}
def test_get_config_returns_fallback_when_no_mysql_and_no_json(self, tmp_path):
"""MySQL 和 JSON 都不存在时返回 fallback。"""
storage = MagicMock()
storage.check_mysql.return_value = False
store = ConfigStore(
storage_manager=storage,
config_dir=tmp_path,
session_maker=None,
)
result = store.get_config("symbols", {"futures": {"默认": "DF2609"}})
assert result == {"futures": {"默认": "DF2609"}}
class TestConfigStoreSet:
"""ConfigStore.set_config 测试。"""
def test_set_config_writes_to_mysql_and_json(self, tmp_path):
"""MySQL 可用时同时写入数据库和 JSON。"""
mock_existing = MagicMock()
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = mock_existing
storage = MagicMock()
storage.check_mysql.return_value = True
store = ConfigStore(
storage_manager=storage,
config_dir=tmp_path,
session_maker=_make_session_maker(mock_db),
)
result = store.set_config("symbols", {"futures": {"沪银": "AG2608"}})
assert result is True
assert mock_existing.config_value == {"futures": {"沪银": "AG2608"}}
mock_db.commit.assert_called_once()
json_path = tmp_path / "symbols_config.json"
assert json_path.exists()
assert json.loads(json_path.read_text(encoding="utf-8")) == {"futures": {"沪银": "AG2608"}}
def test_set_config_writes_only_json_when_mysql_unavailable(self, tmp_path):
"""MySQL 不可用时只写入 JSON。"""
storage = MagicMock()
storage.check_mysql.return_value = False
store = ConfigStore(
storage_manager=storage,
config_dir=tmp_path,
session_maker=None,
)
result = store.set_config("symbols", {"futures": {"沪银": "AG2608"}})
assert result is True
json_path = tmp_path / "symbols_config.json"
assert json_path.exists()
assert json.loads(json_path.read_text(encoding="utf-8")) == {"futures": {"沪银": "AG2608"}}
class TestConfigMigration:
"""配置迁移测试。"""
def test_migrate_skips_when_config_exists(self, tmp_path):
"""数据库已有配置时跳过迁移。"""
from app.config_migration import migrate_configs_to_mysql
with patch("app.config_migration.get_config_store") as mock_get_store:
store = MagicMock()
store._config_exists_in_mysql.return_value = True
store.storage_manager.check_mysql.return_value = True
store.session_maker = MagicMock()
mock_get_store.return_value = store
result = migrate_configs_to_mysql()
assert result is True
store._load_json.assert_not_called()
def test_migrate_loads_json_and_saves(self, tmp_path):
"""从 JSON 读取并保存到数据库。"""
from app.config_migration import migrate_configs_to_mysql
symbols_path = tmp_path / "symbols_config.json"
symbols_path.write_text(json.dumps({"futures": {"沪银": "AG2608"}}, ensure_ascii=False), encoding="utf-8")
ai_path = tmp_path / "ai_config.json"
ai_path.write_text(json.dumps({"models": []}, ensure_ascii=False), encoding="utf-8")
with patch("app.config_migration.get_config_store") as mock_get_store:
store = MagicMock()
store._config_exists_in_mysql.return_value = False
store.storage_manager.check_mysql.return_value = True
store.session_maker = MagicMock()
store.config_dir = tmp_path
store.config_files = {
"symbols": symbols_path,
"ai": ai_path,
}
store._load_json.side_effect = lambda key, default: json.loads(store.config_files[key].read_text(encoding="utf-8")) if store.config_files[key].exists() else default
store._save_to_mysql.return_value = True
mock_get_store.return_value = store
result = migrate_configs_to_mysql()
assert result is True
assert store._save_to_mysql.call_count == 2