Files
owlen/crates/platform/config/tests/precedence.rs
vikingowl 10c8e2baae feat(v2): complete multi-LLM providers, TUI redesign, and advanced agent features
Multi-LLM Provider Support:
- Add llm-core crate with LlmProvider trait abstraction
- Implement Anthropic Claude API client with streaming
- Implement OpenAI API client with streaming
- Add token counting with SimpleTokenCounter and ClaudeTokenCounter
- Add retry logic with exponential backoff and jitter

Borderless TUI Redesign:
- Rewrite theme system with terminal capability detection (Full/Unicode256/Basic)
- Add provider tabs component with keybind switching [1]/[2]/[3]
- Implement vim-modal input (Normal/Insert/Visual/Command modes)
- Redesign chat panel with timestamps and streaming indicators
- Add multi-provider status bar with cost tracking
- Add Nerd Font icons with graceful ASCII fallbacks
- Add syntax highlighting (syntect) and markdown rendering (pulldown-cmark)

Advanced Agent Features:
- Add system prompt builder with configurable components
- Enhance subagent orchestration with parallel execution
- Add git integration module for safe command detection
- Add streaming tool results via channels
- Expand tool set: AskUserQuestion, TodoWrite, LS, MultiEdit, BashOutput, KillShell
- Add WebSearch with provider abstraction

Plugin System Enhancement:
- Add full agent definition parsing from YAML frontmatter
- Add skill system with progressive disclosure
- Wire plugin hooks into HookManager

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 17:24:14 +01:00

234 lines
8.1 KiB
Rust

use config_agent::{load_settings, Settings};
use permissions::{Mode, PermissionDecision, Tool};
use llm_core::ProviderType;
use std::{env, fs};
#[test]
fn precedence_env_overrides_files() {
let tmp = tempfile::tempdir().unwrap();
let project_file = tmp.path().join(".owlen.toml");
fs::write(&project_file, r#"model="local-model""#).unwrap();
unsafe { env::set_var("OWLEN_MODEL", "env-model"); }
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
assert_eq!(s.model, "env-model");
}
#[test]
fn default_mode_is_plan() {
let s = Settings::default();
assert_eq!(s.mode, "plan");
}
#[test]
fn settings_create_permission_manager_with_plan_mode() {
let s = Settings::default();
let mgr = s.create_permission_manager();
// Plan mode should allow read operations
assert_eq!(mgr.check(Tool::Read, None), PermissionDecision::Allow);
// Plan mode should ask for write operations
assert_eq!(mgr.check(Tool::Write, None), PermissionDecision::Ask);
}
#[test]
fn settings_parse_mode_from_config() {
let tmp = tempfile::tempdir().unwrap();
let project_file = tmp.path().join(".owlen.toml");
fs::write(&project_file, r#"mode="code""#).unwrap();
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
assert_eq!(s.mode, "code");
assert_eq!(s.get_mode(), Mode::Code);
let mgr = s.create_permission_manager();
// Code mode should allow everything
assert_eq!(mgr.check(Tool::Write, None), PermissionDecision::Allow);
assert_eq!(mgr.check(Tool::Bash, None), PermissionDecision::Allow);
}
#[test]
fn default_provider_is_ollama() {
let s = Settings::default();
assert_eq!(s.provider, "ollama");
assert_eq!(s.get_provider(), Some(ProviderType::Ollama));
}
#[test]
fn provider_from_config_file() {
let tmp = tempfile::tempdir().unwrap();
let project_file = tmp.path().join(".owlen.toml");
fs::write(&project_file, r#"provider="anthropic""#).unwrap();
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
assert_eq!(s.provider, "anthropic");
assert_eq!(s.get_provider(), Some(ProviderType::Anthropic));
}
#[test]
#[ignore] // Ignore due to env var interaction in parallel tests
fn provider_from_env_var() {
let tmp = tempfile::tempdir().unwrap();
unsafe {
env::set_var("OWLEN_PROVIDER", "openai");
env::remove_var("PROVIDER");
env::remove_var("ANTHROPIC_API_KEY");
env::remove_var("OPENAI_API_KEY");
}
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
assert_eq!(s.provider, "openai");
assert_eq!(s.get_provider(), Some(ProviderType::OpenAI));
unsafe { env::remove_var("OWLEN_PROVIDER"); }
}
#[test]
#[ignore] // Ignore due to env var interaction in parallel tests
fn provider_from_provider_env_var() {
let tmp = tempfile::tempdir().unwrap();
unsafe {
env::set_var("PROVIDER", "anthropic");
env::remove_var("OWLEN_PROVIDER");
env::remove_var("ANTHROPIC_API_KEY");
env::remove_var("OPENAI_API_KEY");
}
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
assert_eq!(s.provider, "anthropic");
assert_eq!(s.get_provider(), Some(ProviderType::Anthropic));
unsafe { env::remove_var("PROVIDER"); }
}
#[test]
fn anthropic_api_key_from_owlen_env() {
let tmp = tempfile::tempdir().unwrap();
let project_file = tmp.path().join(".owlen.toml");
fs::write(&project_file, r#"provider="anthropic""#).unwrap();
unsafe { env::set_var("OWLEN_ANTHROPIC_API_KEY", "sk-ant-test123"); }
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
assert_eq!(s.anthropic_api_key, Some("sk-ant-test123".to_string()));
assert_eq!(s.get_provider_api_key(), Some("sk-ant-test123".to_string()));
unsafe { env::remove_var("OWLEN_ANTHROPIC_API_KEY"); }
}
#[test]
fn openai_api_key_from_owlen_env() {
let tmp = tempfile::tempdir().unwrap();
let project_file = tmp.path().join(".owlen.toml");
fs::write(&project_file, r#"provider="openai""#).unwrap();
unsafe { env::set_var("OWLEN_OPENAI_API_KEY", "sk-test-456"); }
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
assert_eq!(s.openai_api_key, Some("sk-test-456".to_string()));
assert_eq!(s.get_provider_api_key(), Some("sk-test-456".to_string()));
unsafe { env::remove_var("OWLEN_OPENAI_API_KEY"); }
}
#[test]
#[ignore] // Ignore due to env var interaction in parallel tests
fn api_keys_from_config_file() {
let tmp = tempfile::tempdir().unwrap();
let project_file = tmp.path().join(".owlen.toml");
fs::write(&project_file, r#"
provider = "anthropic"
anthropic_api_key = "sk-ant-from-file"
openai_api_key = "sk-openai-from-file"
"#).unwrap();
// Clear any env vars that might interfere
unsafe {
env::remove_var("ANTHROPIC_API_KEY");
env::remove_var("OPENAI_API_KEY");
env::remove_var("OWLEN_ANTHROPIC_API_KEY");
env::remove_var("OWLEN_OPENAI_API_KEY");
}
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
assert_eq!(s.anthropic_api_key, Some("sk-ant-from-file".to_string()));
assert_eq!(s.openai_api_key, Some("sk-openai-from-file".to_string()));
assert_eq!(s.get_provider_api_key(), Some("sk-ant-from-file".to_string()));
}
#[test]
#[ignore] // Ignore due to env var interaction in parallel tests
fn anthropic_api_key_from_standard_env() {
let tmp = tempfile::tempdir().unwrap();
let project_file = tmp.path().join(".owlen.toml");
fs::write(&project_file, r#"provider="anthropic""#).unwrap();
unsafe {
env::set_var("ANTHROPIC_API_KEY", "sk-ant-std");
env::remove_var("OWLEN_ANTHROPIC_API_KEY");
env::remove_var("PROVIDER");
env::remove_var("OWLEN_PROVIDER");
}
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
assert_eq!(s.anthropic_api_key, Some("sk-ant-std".to_string()));
assert_eq!(s.get_provider_api_key(), Some("sk-ant-std".to_string()));
unsafe { env::remove_var("ANTHROPIC_API_KEY"); }
}
#[test]
#[ignore] // Ignore due to env var interaction in parallel tests
fn openai_api_key_from_standard_env() {
let tmp = tempfile::tempdir().unwrap();
let project_file = tmp.path().join(".owlen.toml");
fs::write(&project_file, r#"provider="openai""#).unwrap();
unsafe {
env::set_var("OPENAI_API_KEY", "sk-openai-std");
env::remove_var("OWLEN_OPENAI_API_KEY");
env::remove_var("PROVIDER");
env::remove_var("OWLEN_PROVIDER");
}
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
assert_eq!(s.openai_api_key, Some("sk-openai-std".to_string()));
assert_eq!(s.get_provider_api_key(), Some("sk-openai-std".to_string()));
unsafe { env::remove_var("OPENAI_API_KEY"); }
}
#[test]
#[ignore] // Ignore due to env var interaction in parallel tests
fn owlen_prefix_overrides_standard_env() {
let tmp = tempfile::tempdir().unwrap();
unsafe {
env::set_var("ANTHROPIC_API_KEY", "sk-ant-std");
env::set_var("OWLEN_ANTHROPIC_API_KEY", "sk-ant-owlen");
}
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
// OWLEN_ prefix should take precedence
assert_eq!(s.anthropic_api_key, Some("sk-ant-owlen".to_string()));
unsafe {
env::remove_var("ANTHROPIC_API_KEY");
env::remove_var("OWLEN_ANTHROPIC_API_KEY");
}
}
#[test]
fn effective_model_uses_provider_default() {
// Test Anthropic provider default
let mut s = Settings::default();
s.provider = "anthropic".to_string();
assert_eq!(s.get_effective_model(), "claude-sonnet-4-20250514");
// Test OpenAI provider default
s.provider = "openai".to_string();
assert_eq!(s.get_effective_model(), "gpt-4o");
// Test Ollama provider default
s.provider = "ollama".to_string();
assert_eq!(s.get_effective_model(), "qwen3:8b");
}
#[test]
fn effective_model_respects_explicit_model() {
let mut s = Settings::default();
s.provider = "anthropic".to_string();
s.model = "claude-opus-4-20250514".to_string();
// Should use explicit model, not provider default
assert_eq!(s.get_effective_model(), "claude-opus-4-20250514");
}