130 lines
3.4 KiB
Python
130 lines
3.4 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from llm_trade_bot.config import ConfigError, load_config
|
|
|
|
CONFIG = """
|
|
[zai]
|
|
api_key = "zai-key"
|
|
[bybit]
|
|
api_key = "bybit-key"
|
|
api_secret = "bybit-secret"
|
|
[telegram]
|
|
api_id = 123
|
|
api_hash = "api-hash"
|
|
bot_token = "bot-token"
|
|
admin_user_ids = [42]
|
|
[brave]
|
|
api_key = "brave-key"
|
|
[prompts]
|
|
file_path = "prompts.md"
|
|
[schedule]
|
|
timezone = "Europe/Warsaw"
|
|
times = ["09:00", "21:00"]
|
|
[scanner]
|
|
categories = ["spot", "linear"]
|
|
[storage]
|
|
database_path = "state.db"
|
|
"""
|
|
|
|
PROMPTS = """# Prompts
|
|
## master_prompt
|
|
Master prompt.
|
|
## run_prompt
|
|
Run prompt.
|
|
"""
|
|
|
|
|
|
def write_config(tmp_path: Path, content: str = CONFIG) -> Path:
|
|
path = tmp_path / "config.toml"
|
|
path.write_text(content, encoding="utf-8")
|
|
(tmp_path / "prompts.md").write_text(PROMPTS, encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def test_loads_and_resolves_database_path(tmp_path: Path):
|
|
path = write_config(tmp_path)
|
|
config = load_config(path)
|
|
assert config.zai.model == "glm-4.6"
|
|
assert config.telegram.admin_user_ids == frozenset({42})
|
|
assert config.storage.database_path == tmp_path / "state.db"
|
|
assert config.prompts.file_path == tmp_path / "prompts.md"
|
|
|
|
|
|
def test_rejects_non_official_zai_endpoint(tmp_path: Path):
|
|
changed = CONFIG.replace("api_key = \"zai-key\"", 'api_key = "zai-key"\nbase_url = "https://example.com"', 1)
|
|
path = write_config(tmp_path, changed)
|
|
with pytest.raises(ConfigError, match="base_url"):
|
|
load_config(path)
|
|
|
|
|
|
def test_rejects_missing_prompts_file(tmp_path: Path):
|
|
path = tmp_path / "config.toml"
|
|
path.write_text(CONFIG, encoding="utf-8")
|
|
with pytest.raises(ConfigError, match="Prompts file not found"):
|
|
load_config(path)
|
|
|
|
|
|
def test_legacy_bybit_config_selects_bybit(tmp_path: Path):
|
|
config = load_config(write_config(tmp_path))
|
|
assert config.exchange.provider == "bybit"
|
|
assert config.bybit is not None
|
|
assert config.binance is None
|
|
|
|
|
|
def test_binance_config_only_requires_binance_credentials(tmp_path: Path):
|
|
content = CONFIG.replace(
|
|
'[bybit]\napi_key = "bybit-key"\napi_secret = "bybit-secret"\n',
|
|
'''[exchange]
|
|
provider = "binance"
|
|
[binance.spot]
|
|
api_key = "spot-key"
|
|
api_secret = "spot-secret"
|
|
[binance.usdm]
|
|
api_key = "usdm-key"
|
|
api_secret = "usdm-secret"
|
|
''',
|
|
)
|
|
config = load_config(write_config(tmp_path, content))
|
|
assert config.exchange.provider == "binance"
|
|
assert config.bybit is None
|
|
assert config.binance is not None
|
|
assert config.binance.spot.api_key == "spot-key"
|
|
|
|
|
|
def test_binance_rejects_endpoint_override(tmp_path: Path):
|
|
content = CONFIG.replace(
|
|
'[bybit]\napi_key = "bybit-key"\napi_secret = "bybit-secret"\n',
|
|
'''[exchange]
|
|
provider = "binance"
|
|
[binance]
|
|
base_url = "https://api.binance.com"
|
|
[binance.spot]
|
|
api_key = "spot-key"
|
|
api_secret = "spot-secret"
|
|
[binance.usdm]
|
|
api_key = "usdm-key"
|
|
api_secret = "usdm-secret"
|
|
''',
|
|
)
|
|
with pytest.raises(ConfigError, match="fixed to Testnet"):
|
|
load_config(write_config(tmp_path, content))
|
|
|
|
|
|
def test_binance_requires_both_selected_wallet_credentials(tmp_path: Path):
|
|
content = CONFIG.replace(
|
|
'[bybit]\napi_key = "bybit-key"\napi_secret = "bybit-secret"\n',
|
|
'''[exchange]
|
|
provider = "binance"
|
|
[binance.spot]
|
|
api_key = "spot-key"
|
|
api_secret = "spot-secret"
|
|
[binance.usdm]
|
|
api_key = "replace-me"
|
|
api_secret = "replace-me"
|
|
''',
|
|
)
|
|
with pytest.raises(ConfigError, match="placeholder"):
|
|
load_config(write_config(tmp_path, content))
|