343 lines
11 KiB
Python
343 lines
11 KiB
Python
"""Tests for OnboardingStatus derivation."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from opensquilla.gateway.config import GatewayConfig, LlmProviderConfig
|
|
from opensquilla.onboarding.mutations import upsert_channel
|
|
from opensquilla.onboarding.status import get_onboarding_status
|
|
|
|
|
|
def test_default_provider_with_no_key_needs_onboarding():
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="openrouter",
|
|
model="m",
|
|
api_key="",
|
|
base_url="https://openrouter.ai/api/v1",
|
|
)
|
|
s = get_onboarding_status(cfg)
|
|
assert s.needs_onboarding is True
|
|
|
|
|
|
def test_provider_with_key_is_configured():
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="openrouter",
|
|
model="m",
|
|
api_key="sk-x",
|
|
base_url="https://openrouter.ai/api/v1",
|
|
)
|
|
s = get_onboarding_status(cfg)
|
|
assert s.llm_configured is True
|
|
assert s.llm_source == "explicit"
|
|
assert s.needs_onboarding is False
|
|
|
|
|
|
def test_provider_with_env_key_is_configured(monkeypatch):
|
|
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-from-env")
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="openrouter",
|
|
model="m",
|
|
api_key="",
|
|
api_key_env="OPENROUTER_API_KEY",
|
|
base_url="https://openrouter.ai/api/v1",
|
|
)
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.llm_configured is True
|
|
assert s.llm_source == "env"
|
|
assert s.needs_onboarding is False
|
|
|
|
|
|
def test_provider_with_default_env_var_is_configured(monkeypatch):
|
|
# Config omits api_key and api_key_env; the provider's default env var
|
|
# resolves, so onboarding reports the provider configured (source "env").
|
|
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-from-env")
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="openrouter",
|
|
model="m",
|
|
api_key="",
|
|
base_url="https://openrouter.ai/api/v1",
|
|
)
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.llm_configured is True
|
|
assert s.llm_source == "env"
|
|
assert s.needs_onboarding is False
|
|
|
|
|
|
def test_runtime_secret_marker_keeps_env_source_after_resolution(monkeypatch):
|
|
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-from-env")
|
|
from opensquilla.gateway.llm_runtime import resolve_llm_runtime_config
|
|
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="openrouter",
|
|
model="m",
|
|
api_key="",
|
|
api_key_env="OPENROUTER_API_KEY",
|
|
base_url="https://openrouter.ai/api/v1",
|
|
)
|
|
resolve_llm_runtime_config(cfg)
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert cfg.llm.api_key == "sk-from-env"
|
|
assert "llm.api_key" in cfg._runtime_secret_paths
|
|
assert s.llm_configured is True
|
|
assert s.llm_source == "env"
|
|
|
|
|
|
def test_provider_with_missing_env_key_needs_onboarding(monkeypatch):
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="openrouter",
|
|
model="m",
|
|
api_key="",
|
|
api_key_env="OPENROUTER_API_KEY",
|
|
base_url="https://openrouter.ai/api/v1",
|
|
)
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.llm_configured is False
|
|
assert s.llm_source == "missing_env"
|
|
assert s.needs_onboarding is True
|
|
|
|
|
|
def test_matching_llm_key_does_not_configure_image_generation_until_enabled(monkeypatch):
|
|
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="openrouter",
|
|
model="m",
|
|
api_key="sk-or",
|
|
base_url="https://openrouter.ai/api/v1",
|
|
)
|
|
s = get_onboarding_status(cfg)
|
|
assert s.image_generation_configured is False
|
|
assert s.image_generation_enabled is False
|
|
assert s.image_generation_source == "none"
|
|
assert s.image_generation_provider == ""
|
|
|
|
|
|
def test_enabled_image_generation_can_use_matching_llm_key(monkeypatch):
|
|
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="openrouter",
|
|
model="m",
|
|
api_key="sk-or",
|
|
base_url="https://openrouter.ai/api/v1",
|
|
)
|
|
cfg.image_generation.enabled = True
|
|
s = get_onboarding_status(cfg)
|
|
assert s.image_generation_configured is True
|
|
assert s.image_generation_enabled is True
|
|
assert s.image_generation_source == "llm_fallback"
|
|
assert s.image_generation_provider == "openrouter"
|
|
|
|
|
|
def test_image_generation_disabled_is_not_configured():
|
|
cfg = GatewayConfig()
|
|
cfg.image_generation.enabled = False
|
|
cfg.image_generation.providers.openai.api_key = "sk-openai"
|
|
s = get_onboarding_status(cfg)
|
|
assert s.image_generation_configured is False
|
|
assert s.image_generation_enabled is False
|
|
|
|
|
|
def test_ollama_without_key_is_configured():
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="ollama",
|
|
model="llama3",
|
|
api_key="",
|
|
base_url="http://localhost:11434",
|
|
)
|
|
s = get_onboarding_status(cfg)
|
|
assert s.llm_configured is True
|
|
assert s.memory_embedding_configured is True
|
|
assert s.memory_embedding_provider == "auto"
|
|
assert s.sections["memory_embedding"].value == "ok"
|
|
assert s.section_details["llm"]["required"] is True
|
|
assert s.section_details["channels"]["optional"] is True
|
|
|
|
|
|
def test_no_key_provider_reports_not_required_source():
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="ollama",
|
|
model="llama3",
|
|
api_key="",
|
|
base_url="http://localhost:11434",
|
|
)
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.llm_source == "not_required"
|
|
assert s.section_details["llm"]["detail"] == "no key required"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"provider_id",
|
|
["byteplus_coding_plan", "volcengine_coding_plan"],
|
|
)
|
|
def test_coding_plan_provider_without_key_is_missing_not_key_free(provider_id):
|
|
# Coding-plan providers are runtime-supported and require real env keys;
|
|
# an empty key is missing setup, never "no key required".
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(provider=provider_id, model="m", api_key="")
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.llm_configured is False
|
|
assert s.llm_source == "none"
|
|
assert s.sections["llm"].value == "missing"
|
|
assert "no key required" not in str(s.section_details["llm"])
|
|
|
|
|
|
def test_zero_channels_means_not_messaging_configured():
|
|
cfg = GatewayConfig()
|
|
s = get_onboarding_status(cfg)
|
|
assert s.channel_count == 0
|
|
assert s.channels_configured is False
|
|
|
|
|
|
def test_channel_present_marks_configured():
|
|
cfg = GatewayConfig()
|
|
res = upsert_channel(
|
|
cfg,
|
|
entry_payload={"type": "slack", "name": "w", "token": "x", "signing_secret": "ss"},
|
|
)
|
|
s = get_onboarding_status(res.config)
|
|
assert s.channel_count == 1
|
|
assert s.channels_configured is True
|
|
|
|
|
|
def test_section_details_mark_only_provider_as_first_run_blocking():
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(provider="", model="", api_key="")
|
|
cfg.search_provider = "brave"
|
|
cfg.search_api_key = ""
|
|
cfg.search_api_key_env = ""
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.section_details["llm"]["blocking"] is True
|
|
assert s.section_details["search"]["actionRequired"] is True
|
|
assert s.section_details["search"]["blocking"] is False
|
|
|
|
|
|
def test_router_detail_explains_provider_dependency_before_provider_setup():
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(provider="", model="", api_key="")
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert (
|
|
s.section_details["router"]["detail"]
|
|
== "uses SquillaRouter after provider setup"
|
|
)
|
|
assert s.section_details["router"]["actionRequired"] is False
|
|
|
|
|
|
def test_optional_search_action_does_not_block_first_run():
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="openrouter",
|
|
model="m",
|
|
api_key="sk-x",
|
|
base_url="https://openrouter.ai/api/v1",
|
|
)
|
|
cfg.search_provider = "brave"
|
|
cfg.search_api_key = ""
|
|
cfg.search_api_key_env = ""
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.section_details["search"]["actionRequired"] is True
|
|
assert s.section_details["search"]["blocking"] is False
|
|
assert s.needs_onboarding is False
|
|
|
|
|
|
def test_optional_image_generation_action_does_not_block_first_run(monkeypatch):
|
|
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="deepseek",
|
|
model="m",
|
|
api_key="sk-x",
|
|
base_url="https://api.deepseek.com/v1",
|
|
)
|
|
cfg.image_generation.enabled = True
|
|
cfg.image_generation.primary = "openai/gpt-image-1"
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.section_details["image_generation"]["actionRequired"] is True
|
|
assert s.section_details["image_generation"]["blocking"] is False
|
|
assert s.needs_onboarding is False
|
|
|
|
|
|
def test_explicit_remote_memory_embedding_action_blocks_gateway_start():
|
|
cfg = GatewayConfig()
|
|
cfg.llm = LlmProviderConfig(
|
|
provider="openrouter",
|
|
model="m",
|
|
api_key="sk-x",
|
|
base_url="https://openrouter.ai/api/v1",
|
|
)
|
|
cfg.memory.embedding.provider = "openai"
|
|
cfg.memory.embedding.remote.api_key = ""
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.section_details["memory_embedding"]["actionRequired"] is True
|
|
assert s.section_details["memory_embedding"]["blocking"] is True
|
|
assert s.needs_onboarding is True
|
|
|
|
|
|
def test_ensemble_section_detail_reflects_selection_mode():
|
|
cfg = GatewayConfig()
|
|
cfg.llm_ensemble.enabled = True
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.sections["ensemble"].value == "ok"
|
|
detail = s.section_details["ensemble"]
|
|
assert detail["label"] == "LLM ensemble"
|
|
assert str(detail["detail"]).startswith("selection mode: static_openrouter_b5")
|
|
|
|
|
|
def test_ensemble_section_detail_shows_disabled():
|
|
cfg = GatewayConfig()
|
|
cfg.llm_ensemble.enabled = False
|
|
|
|
s = get_onboarding_status(cfg)
|
|
|
|
assert s.sections["ensemble"].value == "optional"
|
|
assert s.section_details["ensemble"]["detail"] == "disabled"
|
|
|
|
|
|
def test_ensemble_credential_status_reports_unsupported_provider():
|
|
"""The per-provider credential rows must use the same "unsupported"
|
|
enumerant as llm_source for a registered but runtime-unsupported
|
|
provider — never a satisfied-looking "not_required"."""
|
|
from opensquilla.onboarding.status import _llm_provider_credential_status
|
|
|
|
cfg = GatewayConfig()
|
|
row = _llm_provider_credential_status(cfg, "github_copilot")
|
|
|
|
assert row["source"] == "unsupported"
|
|
assert row["available"] is False
|