"""Per-instance TOML config loader tests (REQ-048 / BAT-013).""" from __future__ import annotations import subprocess import sys from pathlib import Path import pytest from trex_core.runtime.config import DaemonConfig, DaemonInstanceConfig _REPO = Path(__file__).resolve().parents[2] def _write_toml(tmp_path, body: str) -> str: p = tmp_path / "inst.toml" p.write_text(body) return str(p) def test_from_toml_parses(tmp_path): """AC-1: from_toml reads instance_key + portfolio_hub_token.""" path = _write_toml(tmp_path, 'instance_key = "inst-1"\nportfolio_hub_token = "tok"\n') cfg = DaemonInstanceConfig.from_toml(path) assert cfg.instance_key == "inst-1" assert cfg.portfolio_hub_token == "tok" def test_from_toml_missing_instance_key_raises(tmp_path): """AC-4: missing required instance_key → error (no silent default).""" path = _write_toml(tmp_path, 'portfolio_hub_token = "tok"\n') with pytest.raises(RuntimeError): DaemonInstanceConfig.from_toml(path) def test_from_config_injects_token(tmp_path, monkeypatch): """AC-3 / S-5: TOML token is injected into PortfolioHubConfig when base_url is set, even with TREX_PORTFOLIO_HUB_TOKEN absent from the environment.""" monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y") monkeypatch.setenv("TREX_PORTFOLIO_HUB_BASE_URL", "http://hub:18000") monkeypatch.delenv("TREX_PORTFOLIO_HUB_TOKEN", raising=False) path = _write_toml(tmp_path, 'instance_key = "inst-9"\nportfolio_hub_token = "toml-tok"\n') cfg = DaemonConfig.from_config(path) assert cfg.instance_key == "inst-9" assert cfg.portfolio_hub is not None assert cfg.portfolio_hub.token == "toml-tok" def test_default_instance_key_when_from_env(monkeypatch): monkeypatch.setenv("TREX_POSTGRESQL_DSN", "postgresql://x/y") assert DaemonConfig.from_env().instance_key == "default" def test_config_required_cli_exits_nonzero(): """AC-2: daemon entry without --config exits non-zero (argparse required).""" proc = subprocess.run( [sys.executable, "-m", "trex_core.runtime.daemon"], capture_output=True, text=True, timeout=30, cwd=str(_REPO), ) assert proc.returncode != 0 assert "config" in (proc.stderr + proc.stdout).lower() def test_gitignore_excludes_instance_toml(): """AC-5: .gitignore excludes instance TOML but keeps the example.""" gi = (_REPO / ".gitignore").read_text() assert "/config/*.toml" in gi assert "!/config/instance.example.toml" in gi