feat(config): integrate OAuth store with config loading
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,35 +12,53 @@ def get_config_path() -> Path:
|
||||
return Path.home() / ".nanobot" / "config.json"
|
||||
|
||||
|
||||
def _get_oauth_store_dir() -> Path:
|
||||
"""Get the OAuth store directory."""
|
||||
return Path.home() / ".nanobot"
|
||||
|
||||
|
||||
def get_data_dir() -> Path:
|
||||
"""Get the nanobot data directory."""
|
||||
from nanobot.utils.helpers import get_data_path
|
||||
return get_data_path()
|
||||
|
||||
|
||||
def _inject_oauth_credentials(config: Config) -> Config:
|
||||
"""Inject OAuth credentials from store into config if available."""
|
||||
from nanobot.config.oauth_store import OAuthStore
|
||||
|
||||
store = OAuthStore(_get_oauth_store_dir())
|
||||
creds = store.load("anthropic")
|
||||
if creds and creds.access_token and not creds.is_expired:
|
||||
config.providers.anthropic.api_key = creds.access_token
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def load_config(config_path: Path | None = None) -> Config:
|
||||
"""
|
||||
Load configuration from file or create default.
|
||||
|
||||
|
||||
Args:
|
||||
config_path: Optional path to config file. Uses default if not provided.
|
||||
|
||||
|
||||
Returns:
|
||||
Loaded configuration object.
|
||||
"""
|
||||
path = config_path or get_config_path()
|
||||
|
||||
|
||||
if path.exists():
|
||||
try:
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
data = _migrate_config(data)
|
||||
return Config.model_validate(convert_keys(data))
|
||||
config = Config.model_validate(convert_keys(data))
|
||||
return _inject_oauth_credentials(config)
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
print(f"Warning: Failed to load config from {path}: {e}")
|
||||
print("Using default configuration.")
|
||||
|
||||
return Config()
|
||||
|
||||
return _inject_oauth_credentials(Config())
|
||||
|
||||
|
||||
def save_config(config: Config, config_path: Path | None = None) -> None:
|
||||
|
||||
65
tests/test_config_oauth_integration.py
Normal file
65
tests/test_config_oauth_integration.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""Test OAuth store integration with config loading."""
|
||||
import json
|
||||
import pytest
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from nanobot.config.loader import load_config
|
||||
from nanobot.config.oauth_store import OAuthStore
|
||||
from nanobot.config.schema import OAuthCredentials
|
||||
|
||||
|
||||
def test_oauth_token_injected_into_config(tmp_path, monkeypatch):
|
||||
"""OAuth token from store should be injected into provider api_key."""
|
||||
# Create a minimal config file (no api key set)
|
||||
config_path = tmp_path / "config.json"
|
||||
config_path.write_text(json.dumps({
|
||||
"agents": {"defaults": {"model": "anthropic/claude-opus-4-5"}},
|
||||
"providers": {"anthropic": {"apiKey": ""}}
|
||||
}))
|
||||
|
||||
# Save OAuth credentials
|
||||
store = OAuthStore(tmp_path)
|
||||
creds = OAuthCredentials(access_token="sk-ant-oat01-test-inject")
|
||||
store.save("anthropic", creds)
|
||||
|
||||
# Monkeypatch get_config_path to use our tmp dir
|
||||
monkeypatch.setattr("nanobot.config.loader.get_config_path", lambda: config_path)
|
||||
# Monkeypatch the OAuth store path
|
||||
monkeypatch.setattr("nanobot.config.loader._get_oauth_store_dir", lambda: tmp_path)
|
||||
|
||||
config = load_config(config_path)
|
||||
|
||||
assert config.providers.anthropic.api_key == "sk-ant-oat01-test-inject"
|
||||
|
||||
|
||||
def test_config_without_oauth_unchanged(tmp_path, monkeypatch):
|
||||
"""Config without OAuth store should load normally."""
|
||||
config_path = tmp_path / "config.json"
|
||||
config_path.write_text(json.dumps({
|
||||
"providers": {"anthropic": {"apiKey": "sk-ant-api03-regular"}}
|
||||
}))
|
||||
|
||||
monkeypatch.setattr("nanobot.config.loader._get_oauth_store_dir", lambda: tmp_path / "nonexistent")
|
||||
|
||||
config = load_config(config_path)
|
||||
|
||||
assert config.providers.anthropic.api_key == "sk-ant-api03-regular"
|
||||
|
||||
|
||||
def test_oauth_does_not_overwrite_existing_key(tmp_path, monkeypatch):
|
||||
"""If user already has an API key, OAuth should still override (OAuth takes priority)."""
|
||||
config_path = tmp_path / "config.json"
|
||||
config_path.write_text(json.dumps({
|
||||
"providers": {"anthropic": {"apiKey": "sk-ant-api03-existing"}}
|
||||
}))
|
||||
|
||||
store = OAuthStore(tmp_path)
|
||||
creds = OAuthCredentials(access_token="sk-ant-oat01-oauth-wins")
|
||||
store.save("anthropic", creds)
|
||||
|
||||
monkeypatch.setattr("nanobot.config.loader._get_oauth_store_dir", lambda: tmp_path)
|
||||
|
||||
config = load_config(config_path)
|
||||
|
||||
# OAuth token takes priority over existing API key
|
||||
assert config.providers.anthropic.api_key == "sk-ant-oat01-oauth-wins"
|
||||
Reference in New Issue
Block a user