"""Tests for the shared onboarding setup engine.""" from __future__ import annotations import tomllib from opensquilla.onboarding.config_store import load_config from opensquilla.onboarding.setup_engine import SetupEngine def test_setup_engine_applies_provider_and_router_without_persisting_secret(tmp_path): target = tmp_path / "config.toml" engine = SetupEngine(path=target) engine.apply( "provider", { "providerId": "deepseek", "model": "deepseek-chat", "apiKeyEnv": "DEEPSEEK_API_KEY", }, ) engine.apply("router", {"mode": "recommended"}) result = engine.persist() data = tomllib.loads(target.read_text()) assert result.path == target assert data["llm"]["provider"] == "deepseek" assert data["llm"]["api_key_env"] == "DEEPSEEK_API_KEY" assert "api_key" not in data["llm"] assert data["squilla_router"]["tier_profile"] == "deepseek" assert "tiers" not in data["squilla_router"] def test_setup_engine_can_derive_provider_model_from_router_default_tier(tmp_path): target = tmp_path / "config.toml" engine = SetupEngine(path=target) engine.apply( "provider", { "providerId": "deepseek", "apiKeyEnv": "DEEPSEEK_API_KEY", }, ) result = engine.persist() data = tomllib.loads(target.read_text()) assert result.path == target assert data["llm"]["provider"] == "deepseek" assert data["llm"]["model"] == "deepseek-v4-flash" assert data["squilla_router"]["tier_profile"] == "deepseek" assert load_config(target).squilla_router.default_tier == "c1" def test_setup_engine_passes_preset_id_through_to_provider_upsert(tmp_path): # The CLI's --preset flag rides this payload key; a synthesized preset # must persist the custom-mode shape (no tier_profile, expanded tiers). target = tmp_path / "config.toml" engine = SetupEngine(path=target) engine.apply( "provider", { "providerId": "groq", "model": "llama-3.3-70b", "apiKeyEnv": "GROQ_API_KEY", "presetId": "groq", }, ) engine.persist() data = tomllib.loads(target.read_text()) assert data["llm"]["provider"] == "groq" assert load_config(target).squilla_router.enabled is True assert "tier_profile" not in data["squilla_router"] tier = data["squilla_router"]["tiers"]["c0"] assert tier["provider"] == "groq" assert tier["model"] == "llama-3.3-70b" def test_setup_engine_router_tier_override_updates_direct_fallback_model(tmp_path): target = tmp_path / "config.toml" engine = SetupEngine(path=target) engine.apply( "provider", { "providerId": "openai", "apiKeyEnv": "OPENAI_API_KEY", }, ) engine.apply( "router", { "mode": "recommended", "defaultTier": "c2", "tiers": { "c2": { "provider": "openai", "model": "gpt-5.5-custom", "thinkingLevel": "high", } }, }, ) engine.persist() data = tomllib.loads(target.read_text()) assert data["llm"]["model"] == "gpt-5.5-custom" assert "tier_profile" not in data["squilla_router"] assert data["squilla_router"]["default_tier"] == "c2" assert data["squilla_router"]["tiers"]["c2"]["model"] == "gpt-5.5-custom" assert data["squilla_router"]["tiers"]["c2"]["thinking_level"] == "high" def test_setup_engine_next_steps_do_not_include_secret(tmp_path): engine = SetupEngine(path=tmp_path / "config.toml") engine.apply( "provider", { "providerId": "openrouter", "model": "deepseek/deepseek-v4-flash", "apiKey": "sk-secret", }, ) text = engine.preview_next_steps() assert "sk-secret" not in text assert "opensquilla gateway start" in text assert "openrouter" in text def test_setup_engine_image_generation_can_use_custom_env_reference( tmp_path, monkeypatch, ): monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) monkeypatch.setenv("OPENSQUILLA_TEST_IMAGE_KEY", "sk-image-env") target = tmp_path / "config.toml" engine = SetupEngine(path=target) engine.apply( "image-generation", { "providerId": "openrouter", "primary": "openrouter/google/gemini-3.1-flash-image-preview", "apiKeyEnv": "OPENSQUILLA_TEST_IMAGE_KEY", }, ) engine.persist() data = tomllib.loads(target.read_text()) provider = data["image_generation"]["providers"]["openrouter"] assert load_config(target).image_generation.providers.openrouter.api_key == "" assert provider["api_key_env"] == "OPENSQUILLA_TEST_IMAGE_KEY" def test_setup_engine_accepts_short_capability_section_aliases(tmp_path, monkeypatch): monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) target = tmp_path / "config.toml" engine = SetupEngine(path=target) engine.apply("image", {"enabled": False}) engine.apply( "memory", { "providerId": "local", "onnxDir": "models/bge", }, ) engine.persist() data = tomllib.loads(target.read_text()) assert load_config(target).image_generation.enabled is False assert data["memory"]["embedding"]["provider"] == "local" assert data["memory"]["embedding"]["local"]["onnx_dir"] == "models/bge" def test_setup_engine_catalog_includes_memory_embedding(): engine = SetupEngine() payload = engine.catalog("memory-embedding") provider_ids = {p["providerId"] for p in payload["memoryEmbeddingProviders"]} assert {"auto", "local", "openai", "ollama", "none"} <= provider_ids assert all("whatYouNeed" in p for p in payload["memoryEmbeddingProviders"]) def test_setup_engine_applies_ensemble_with_keep_current_semantics(tmp_path): import pytest target = tmp_path / "config.toml" engine = SetupEngine(path=target) engine.apply( "ensemble", { "enabled": True, "selectionMode": "router_dynamic", "modelOptions": ["prov/model-a", "prov/model-b"], "minSuccessfulProposers": 2, "allFailedPolicy": "error", }, ) engine.persist() data = tomllib.loads(target.read_text()) ensemble = data["llm_ensemble"] assert ensemble["enabled"] is True assert ensemble["selection_mode"] == "router_dynamic" assert ensemble["model_options"] == ["prov/model-a", "prov/model-b"] assert ensemble["min_successful_proposers"] == 2 assert ensemble["all_failed_policy"] == "error" # A partial payload must only touch the keys it names. second = SetupEngine(path=target) second.apply("ensemble", {"enabled": False}) second.persist() data = tomllib.loads(target.read_text()) ensemble = data["llm_ensemble"] assert ensemble["enabled"] is False assert ensemble["selection_mode"] == "router_dynamic" assert ensemble["model_options"] == ["prov/model-a", "prov/model-b"] assert ensemble["min_successful_proposers"] == 2 assert ensemble["all_failed_policy"] == "error" with pytest.raises(ValueError, match="modelOptions must be a list"): SetupEngine(path=target).apply("ensemble", {"modelOptions": "not-a-list"}) def test_setup_engine_accepts_ensemble_section_aliases(tmp_path): import tomllib as _tomllib for alias in ("ensemble", "llm-ensemble", "llm_ensemble"): target = tmp_path / f"{alias.replace('_', '-')}.toml" # Seed the non-default state: the alias must observably apply the # disable, not exit cleanly while persisting nothing (the effective # value equals the model default on a fresh config, so a no-op # would previously pass this test). target.write_text("[llm_ensemble]\nenabled = true\n", encoding="utf-8") engine = SetupEngine(path=target) engine.apply(alias, {"enabled": False}) engine.persist() data = _tomllib.loads(target.read_text()) assert data["llm_ensemble"]["enabled"] is False, alias assert load_config(target).llm_ensemble.enabled is False def test_setup_engine_provider_none_payload_values_keep_stored_fields(tmp_path): # None payload values mean "not passed": a same-provider re-save keeps # the stored model/base_url/proxy instead of coercing None to "None" or # resetting to derived defaults. target = tmp_path / "config.toml" target.write_text( "[llm]\n" 'provider = "openrouter"\n' 'model = "custom/model-x"\n' 'api_key = "sk-old"\n' 'base_url = "https://gateway.example.test/v1"\n' 'proxy = "http://127.0.0.1:7890"\n', encoding="utf-8", ) engine = SetupEngine(path=target) engine.apply( "provider", { "providerId": "openrouter", "model": None, "apiKey": "sk-new", "apiKeyEnv": None, "baseUrl": None, "proxy": None, }, ) engine.persist() cfg = load_config(target) assert cfg.llm.model == "custom/model-x" assert cfg.llm.base_url == "https://gateway.example.test/v1" assert cfg.llm.proxy == "http://127.0.0.1:7890" assert cfg.llm.api_key == "sk-new" def test_setup_engine_search_none_payload_values_keep_stored_settings(tmp_path): target = tmp_path / "config.toml" target.write_text( 'search_provider = "brave"\n' 'search_api_key = "sk-old"\n' "search_max_results = 9\n" 'search_proxy = "http://127.0.0.1:7890"\n' "search_use_env_proxy = true\n" 'search_fallback_policy = "network"\n' "search_diagnostics = true\n", encoding="utf-8", ) engine = SetupEngine(path=target) engine.apply( "search", { "providerId": "brave", "apiKey": "sk-new", "maxResults": None, "proxy": None, "useEnvProxy": None, "fallbackPolicy": None, "diagnostics": None, }, ) engine.persist() cfg = load_config(target) assert cfg.search_max_results == 9 assert cfg.search_proxy == "http://127.0.0.1:7890" assert cfg.search_use_env_proxy is True assert cfg.search_fallback_policy == "network" assert cfg.search_diagnostics is True assert cfg.search_api_key == "sk-new" def test_setup_engine_image_enabled_none_keeps_stored_disabled_flag( tmp_path, monkeypatch ): monkeypatch.setenv("OPENSQUILLA_TEST_IMAGE_KEY", "sk-image-env") target = tmp_path / "config.toml" target.write_text( "[image_generation]\n" "enabled = false\n" 'primary = "openrouter/google/gemini-3.1-flash-image-preview"\n' "\n" "[image_generation.providers.openrouter]\n" 'api_key_env = "OPENSQUILLA_TEST_IMAGE_KEY"\n', encoding="utf-8", ) engine = SetupEngine(path=target) engine.apply( "image-generation", { "providerId": "openrouter", "primary": "openrouter/google/gemini-3.1-flash-image-preview", "apiKeyEnv": "OPENSQUILLA_TEST_IMAGE_KEY", "enabled": None, }, ) engine.persist() assert load_config(target).image_generation.enabled is False def test_setup_engine_image_enabled_none_defaults_to_enabled_for_fresh_config( tmp_path, monkeypatch ): # A config that never stored the flag keeps the legacy # configure-implies-enable behavior for a first-time setup. monkeypatch.setenv("OPENROUTER_API_KEY", "sk-image-env") target = tmp_path / "config.toml" engine = SetupEngine(path=target) engine.apply( "image-generation", { "providerId": "openrouter", "primary": "openrouter/google/gemini-3.1-flash-image-preview", "enabled": None, }, ) engine.persist() assert load_config(target).image_generation.enabled is True