217 lines
9.1 KiB
Python
217 lines
9.1 KiB
Python
"""Tests for onboarding wizard and chat command."""
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import yaml
|
|
|
|
from agentkit.cli.onboarding import (
|
|
PROVIDER_PRESETS,
|
|
needs_onboarding,
|
|
run_onboarding,
|
|
)
|
|
|
|
|
|
class TestNeedsOnboarding:
|
|
def test_needs_onboarding_when_no_config(self, tmp_path, monkeypatch):
|
|
"""Should return True when no config file exists."""
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.delenv("AGENTKIT_CONFIG_PATH", raising=False)
|
|
assert needs_onboarding() is True
|
|
|
|
def test_no_onboarding_when_config_exists(self, tmp_path, monkeypatch):
|
|
"""Should return False when agentkit.yaml exists."""
|
|
config_file = tmp_path / "agentkit.yaml"
|
|
config_file.write_text("server:\n port: 8001\n")
|
|
monkeypatch.chdir(tmp_path)
|
|
assert needs_onboarding(config_arg=str(config_file)) is False
|
|
|
|
def test_no_onboarding_with_home_config(self, tmp_path, monkeypatch):
|
|
"""Should return False when ~/.agentkit/agentkit.yaml exists."""
|
|
home_dir = tmp_path / "home"
|
|
home_dir.mkdir()
|
|
agentkit_dir = home_dir / ".agentkit"
|
|
agentkit_dir.mkdir()
|
|
(agentkit_dir / "agentkit.yaml").write_text("server:\n port: 8001\n")
|
|
monkeypatch.setenv("HOME", str(home_dir))
|
|
monkeypatch.chdir(tmp_path / "empty" if (tmp_path / "empty").exists() else tmp_path)
|
|
# Create empty cwd to ensure no local config
|
|
empty_dir = tmp_path / "empty"
|
|
empty_dir.mkdir()
|
|
monkeypatch.chdir(empty_dir)
|
|
assert needs_onboarding() is False
|
|
|
|
|
|
class TestProviderPresets:
|
|
def test_all_presets_have_required_fields(self):
|
|
"""Every provider preset must have name, env_key, base_url, models."""
|
|
for key, preset in PROVIDER_PRESETS.items():
|
|
assert "name" in preset, f"{key} missing name"
|
|
assert "env_key" in preset, f"{key} missing env_key"
|
|
assert "base_url" in preset, f"{key} missing base_url"
|
|
assert "models" in preset, f"{key} missing models"
|
|
assert preset["models"], f"{key} has empty models"
|
|
assert "default_model" in preset, f"{key} missing default_model"
|
|
|
|
def test_preset_keys_are_lowercase(self):
|
|
"""Provider keys should be lowercase."""
|
|
for key in PROVIDER_PRESETS:
|
|
assert key == key.lower(), f"Provider key '{key}' should be lowercase"
|
|
|
|
def test_deepseek_preset(self):
|
|
"""DeepSeek preset should have correct configuration."""
|
|
ds = PROVIDER_PRESETS["deepseek"]
|
|
assert ds["env_key"] == "DEEPSEEK_API_KEY"
|
|
assert "deepseek-chat" in ds["models"]
|
|
assert ds["type"] == "openai"
|
|
|
|
def test_qwen_preset(self):
|
|
"""Qwen preset should use DashScope endpoint."""
|
|
qwen = PROVIDER_PRESETS["qwen"]
|
|
assert "dashscope" in qwen["base_url"]
|
|
assert qwen["env_key"] == "DASHSCOPE_API_KEY"
|
|
|
|
|
|
class TestRunOnboarding:
|
|
def test_onboarding_generates_config_files(self, tmp_path, monkeypatch):
|
|
"""Onboarding should generate agentkit.yaml and .env."""
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.setenv("HOME", str(tmp_path / "home"))
|
|
(tmp_path / "home").mkdir(exist_ok=True)
|
|
|
|
# Mock user input
|
|
with patch("agentkit.cli.onboarding.Prompt") as mock_prompt, \
|
|
patch("agentkit.cli.onboarding.Confirm") as mock_confirm:
|
|
# Step 1: Select DeepSeek (option 1)
|
|
# Step 2: API key
|
|
# Step 2b: Select model (1 = deepseek-chat, default)
|
|
# Step 5: Agent personality (name, personality, speaking_style)
|
|
mock_prompt.ask.side_effect = ["1", "sk-test-deepseek-key", "1", "小王", "友好耐心", "简洁专业"]
|
|
# Step 3: No second provider
|
|
mock_confirm.ask.return_value = False
|
|
|
|
config_path = run_onboarding(output_dir=str(tmp_path))
|
|
|
|
assert config_path is not None
|
|
assert Path(config_path).exists()
|
|
|
|
# Verify agentkit.yaml content
|
|
with open(config_path) as f:
|
|
config = yaml.safe_load(f)
|
|
assert "llm" in config
|
|
assert "deepseek" in config["llm"]["providers"]
|
|
assert config["llm"]["providers"]["deepseek"]["base_url"] == "https://api.deepseek.com/v1"
|
|
|
|
# Verify .env content
|
|
env_path = tmp_path / ".env"
|
|
assert env_path.exists()
|
|
env_content = env_path.read_text()
|
|
assert "DEEPSEEK_API_KEY=sk-test-deepseek-key" in env_content
|
|
|
|
def test_onboarding_with_two_providers(self, tmp_path, monkeypatch):
|
|
"""Onboarding should support adding a second provider."""
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.setenv("HOME", str(tmp_path / "home"))
|
|
(tmp_path / "home").mkdir(exist_ok=True)
|
|
|
|
with patch("agentkit.cli.onboarding.Prompt") as mock_prompt, \
|
|
patch("agentkit.cli.onboarding.Confirm") as mock_confirm:
|
|
# Select DeepSeek (1), API key, model (1), then Qwen as second
|
|
# After removing deepseek, remaining = [openai, bailian-coding, qwen, doubao, gemini, anthropic]
|
|
# qwen is at index 2, so option 3
|
|
# Step 5: Agent personality defaults
|
|
mock_prompt.ask.side_effect = ["1", "sk-deepseek", "1", "3", "sk-dashscope", "1", "AgentKit", "专业、友好、注重细节", "简洁清晰"]
|
|
mock_confirm.ask.return_value = True
|
|
|
|
config_path = run_onboarding(output_dir=str(tmp_path))
|
|
|
|
with open(config_path) as f:
|
|
config = yaml.safe_load(f)
|
|
|
|
providers = config["llm"]["providers"]
|
|
assert "deepseek" in providers
|
|
assert "qwen" in providers
|
|
|
|
env_path = tmp_path / ".env"
|
|
env_content = env_path.read_text()
|
|
assert "DEEPSEEK_API_KEY=sk-deepseek" in env_content
|
|
assert "DASHSCOPE_API_KEY=sk-dashscope" in env_content
|
|
|
|
def test_onboarding_cancelled_on_empty_api_key(self, tmp_path, monkeypatch):
|
|
"""Onboarding should return None if API key is empty."""
|
|
monkeypatch.chdir(tmp_path)
|
|
|
|
with patch("agentkit.cli.onboarding.Prompt") as mock_prompt:
|
|
mock_prompt.ask.side_effect = ["1", ""] # Empty API key
|
|
|
|
result = run_onboarding(output_dir=str(tmp_path))
|
|
|
|
assert result is None
|
|
|
|
def test_onboarding_config_has_memory_backend(self, tmp_path, monkeypatch):
|
|
"""Generated config should use memory backends by default."""
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.setenv("HOME", str(tmp_path / "home"))
|
|
(tmp_path / "home").mkdir(exist_ok=True)
|
|
|
|
with patch("agentkit.cli.onboarding.Prompt") as mock_prompt, \
|
|
patch("agentkit.cli.onboarding.Confirm") as mock_confirm:
|
|
mock_prompt.ask.side_effect = ["1", "sk-test-key", "1", "AgentKit", "专业、友好、注重细节", "简洁清晰"]
|
|
mock_confirm.ask.return_value = False
|
|
|
|
config_path = run_onboarding(output_dir=str(tmp_path))
|
|
|
|
with open(config_path) as f:
|
|
config = yaml.safe_load(f)
|
|
|
|
assert config["session"]["backend"] == "memory"
|
|
assert config["bus"]["backend"] == "memory"
|
|
assert config["task_store"]["backend"] == "memory"
|
|
|
|
|
|
class TestOnboardingSoulGeneration:
|
|
"""U5: Onboarding 生成 SOUL.md 测试."""
|
|
|
|
def test_onboarding_creates_soul_md(self, tmp_path, monkeypatch):
|
|
"""Onboarding should create SOUL.md with custom agent name."""
|
|
monkeypatch.chdir(tmp_path)
|
|
home_dir = tmp_path / "home"
|
|
home_dir.mkdir(exist_ok=True)
|
|
monkeypatch.setenv("HOME", str(home_dir))
|
|
|
|
with patch("agentkit.cli.onboarding.Prompt") as mock_prompt, \
|
|
patch("agentkit.cli.onboarding.Confirm") as mock_confirm:
|
|
mock_prompt.ask.side_effect = ["1", "sk-test-key", "1", "小王", "友好耐心", "简洁专业"]
|
|
mock_confirm.ask.return_value = False
|
|
|
|
run_onboarding(output_dir=str(tmp_path))
|
|
|
|
# Verify SOUL.md was created
|
|
soul_path = home_dir / ".agentkit" / "SOUL.md"
|
|
assert soul_path.exists()
|
|
soul_content = soul_path.read_text(encoding="utf-8")
|
|
assert "小王" in soul_content
|
|
assert "友好耐心" in soul_content
|
|
assert "简洁专业" in soul_content
|
|
|
|
def test_onboarding_soul_with_defaults(self, tmp_path, monkeypatch):
|
|
"""Onboarding with default personality should create default SOUL.md."""
|
|
monkeypatch.chdir(tmp_path)
|
|
home_dir = tmp_path / "home"
|
|
home_dir.mkdir(exist_ok=True)
|
|
monkeypatch.setenv("HOME", str(home_dir))
|
|
|
|
with patch("agentkit.cli.onboarding.Prompt") as mock_prompt, \
|
|
patch("agentkit.cli.onboarding.Confirm") as mock_confirm:
|
|
# Prompt.ask returns the default value when user presses Enter
|
|
# Our mock needs to return the actual default values
|
|
mock_prompt.ask.side_effect = ["1", "sk-test-key", "1", "AgentKit", "专业、友好、注重细节", "简洁清晰"]
|
|
mock_confirm.ask.return_value = False
|
|
|
|
run_onboarding(output_dir=str(tmp_path))
|
|
|
|
soul_path = home_dir / ".agentkit" / "SOUL.md"
|
|
assert soul_path.exists()
|
|
soul_content = soul_path.read_text(encoding="utf-8")
|
|
assert "AgentKit" in soul_content
|