| from __future__ import annotations |
|
|
| import json |
|
|
| import pytest |
|
|
| from world_simulator.config import apply_runtime_env_overrides, load_game_config, parse_game_config |
|
|
|
|
| def test_parse_game_config() -> None: |
| config = parse_game_config( |
| { |
| "world": {"width": 80, "depth": 60, "terrain": "plain_green", "seed": 7}, |
| "npcs": {"count": 3}, |
| "simulation": {"tick_ms": 500}, |
| "server": {"host": "127.0.0.1", "port": 8000}, |
| } |
| ) |
|
|
| assert config.world.width == 80 |
| assert config.world.depth == 60 |
| assert config.npcs.count == 3 |
| assert config.world.seed == 7 |
| assert config.connector.type == "deterministic" |
|
|
|
|
| def test_parse_openai_compatible_connector() -> None: |
| config = parse_game_config( |
| { |
| "world": {"width": 80, "depth": 60, "terrain": "plain_green", "seed": 7}, |
| "npcs": {"count": 3}, |
| "simulation": {"tick_ms": 500}, |
| "server": {"host": "127.0.0.1", "port": 8000}, |
| "connector": { |
| "type": "openai_compatible", |
| "base_url": "http://llm.local/v1", |
| "model": "test-model", |
| "max_tokens": 256, |
| "temperature": 0.6, |
| "top_p": 0.95, |
| "tool_choice": "required", |
| "max_parallel_npc_calls": 3, |
| }, |
| "god_console": { |
| "type": "openai_compatible", |
| "base_url": "https://georgij-sawin--world-simulator-qwen36-27b-h100-serve.modal.run/v1", |
| "model": "qwen3.6-27b-h100-fp8", |
| "max_tokens": 1200, |
| "tool_choice": "required", |
| }, |
| } |
| ) |
|
|
| assert config.connector.type == "openai_compatible" |
| assert config.connector.base_url == "http://llm.local/v1" |
| assert config.connector.model == "test-model" |
| assert config.connector.max_tokens == 256 |
| assert config.connector.temperature == 0.6 |
| assert config.connector.top_p == 0.95 |
| assert config.connector.tool_choice == "required" |
| assert config.connector.max_parallel_npc_calls == 3 |
| assert config.god_console is not None |
| assert ( |
| config.god_console.base_url |
| == "https://georgij-sawin--world-simulator-qwen36-27b-h100-serve.modal.run/v1" |
| ) |
| assert config.god_console.model == "qwen3.6-27b-h100-fp8" |
| assert config.god_console.max_tokens == 1200 |
| assert config.god_console.tool_choice == "required" |
|
|
|
|
| def test_rejects_negative_npc_count() -> None: |
| with pytest.raises(ValueError, match="count"): |
| parse_game_config( |
| { |
| "world": {"width": 80, "depth": 60, "terrain": "plain_green", "seed": 7}, |
| "npcs": {"count": -1}, |
| "simulation": {"tick_ms": 500}, |
| "server": {"host": "127.0.0.1", "port": 8000}, |
| } |
| ) |
|
|
|
|
| def test_load_game_config_reads_dotenv(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: |
| monkeypatch.chdir(tmp_path) |
| config_dir = tmp_path / "config" |
| config_dir.mkdir() |
| config_path = config_dir / "game.json" |
| config_path.write_text( |
| json.dumps( |
| { |
| "world": {"width": 80, "depth": 60, "terrain": "plain_green", "seed": 7}, |
| "npcs": {"count": 3}, |
| "simulation": {"tick_ms": 500}, |
| "server": {"host": "127.0.0.1", "port": 8000}, |
| "connector": { |
| "type": "openai_compatible", |
| "base_url_env": "TEST_OPENAI_BASE_URL", |
| "model_env": "TEST_OPENAI_MODEL", |
| "api_key_env": "TEST_OPENAI_API_KEY", |
| }, |
| } |
| ), |
| encoding="utf-8", |
| ) |
| (tmp_path / ".env").write_text( |
| "\n".join( |
| [ |
| "TEST_OPENAI_BASE_URL=https://api.openai.com/v1", |
| "TEST_OPENAI_MODEL=your-tool-capable-model", |
| "TEST_OPENAI_API_KEY=test-key", |
| ] |
| ), |
| encoding="utf-8", |
| ) |
|
|
| config = load_game_config(config_path) |
|
|
| assert config.connector.base_url == "https://api.openai.com/v1" |
| assert config.connector.model == "your-tool-capable-model" |
| assert config.connector.api_key_env == "TEST_OPENAI_API_KEY" |
|
|
|
|
| def test_load_game_config_does_not_override_existing_env( |
| tmp_path, |
| monkeypatch: pytest.MonkeyPatch, |
| ) -> None: |
| monkeypatch.chdir(tmp_path) |
| monkeypatch.setenv("TEST_OPENAI_BASE_URL", "https://already-set.example/v1") |
| config_path = tmp_path / "game.json" |
| config_path.write_text( |
| json.dumps( |
| { |
| "world": {"width": 80, "depth": 60, "terrain": "plain_green", "seed": 7}, |
| "npcs": {"count": 3}, |
| "simulation": {"tick_ms": 500}, |
| "server": {"host": "127.0.0.1", "port": 8000}, |
| "connector": { |
| "type": "openai_compatible", |
| "base_url_env": "TEST_OPENAI_BASE_URL", |
| "model": "test-model", |
| }, |
| } |
| ), |
| encoding="utf-8", |
| ) |
| (tmp_path / ".env").write_text( |
| "TEST_OPENAI_BASE_URL=https://from-dotenv.example/v1", |
| encoding="utf-8", |
| ) |
|
|
| config = load_game_config(config_path) |
|
|
| assert config.connector.base_url == "https://already-set.example/v1" |
|
|
|
|
| def test_force_deterministic_env_overrides_runtime_connectors( |
| monkeypatch: pytest.MonkeyPatch, |
| ) -> None: |
| monkeypatch.setenv("WORLD_SIMULATOR_FORCE_DETERMINISTIC", "1") |
| config = parse_game_config( |
| { |
| "world": {"width": 80, "depth": 60, "terrain": "plain_green", "seed": 7}, |
| "npcs": {"count": 3}, |
| "simulation": {"tick_ms": 500}, |
| "server": {"host": "127.0.0.1", "port": 8000}, |
| "connector": { |
| "type": "openai_compatible", |
| "base_url": "https://workspace--npc-serve.modal.run/v1", |
| "model": "npc-model", |
| }, |
| "god_console": { |
| "type": "openai_compatible", |
| "base_url": "https://workspace--god-serve.modal.run/v1", |
| "model": "god-model", |
| }, |
| "overseer": { |
| "mode": "autopilot", |
| "connector": { |
| "type": "openai_compatible", |
| "base_url": "https://workspace--overseer-serve.modal.run/v1", |
| "model": "overseer-model", |
| }, |
| }, |
| } |
| ) |
|
|
| config = apply_runtime_env_overrides(config) |
|
|
| assert config.connector.type == "deterministic" |
| assert config.god_console is None |
| assert config.overseer.mode == "autopilot" |
| assert config.overseer.connector.type == "deterministic" |
|
|