106 lines
3.2 KiB
Python
106 lines
3.2 KiB
Python
|
|
"""Tests for markitect.llm.config."""
|
||
|
|
|
||
|
|
import os
|
||
|
|
from pathlib import Path
|
||
|
|
from unittest import mock
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from markitect.llm.config import (
|
||
|
|
LLMConfig,
|
||
|
|
resolve_api_key,
|
||
|
|
find_project_root,
|
||
|
|
load_config,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestResolveApiKey:
|
||
|
|
def test_explicit_key_wins(self):
|
||
|
|
key = resolve_api_key(explicit="sk-explicit")
|
||
|
|
assert key == "sk-explicit"
|
||
|
|
|
||
|
|
def test_env_var_fallback(self):
|
||
|
|
with mock.patch.dict(os.environ, {"MY_KEY": "sk-env"}):
|
||
|
|
key = resolve_api_key(explicit=None, env_var="MY_KEY")
|
||
|
|
assert key == "sk-env"
|
||
|
|
|
||
|
|
def test_env_var_stripped(self):
|
||
|
|
with mock.patch.dict(os.environ, {"MY_KEY": " sk-env \n"}):
|
||
|
|
key = resolve_api_key(explicit=None, env_var="MY_KEY")
|
||
|
|
assert key == "sk-env"
|
||
|
|
|
||
|
|
def test_file_fallback(self, tmp_path):
|
||
|
|
key_file = tmp_path / "key.txt"
|
||
|
|
key_file.write_text("sk-file\n")
|
||
|
|
key = resolve_api_key(
|
||
|
|
explicit=None,
|
||
|
|
env_var="NONEXISTENT_VAR_XYZ",
|
||
|
|
key_file_paths=[key_file],
|
||
|
|
)
|
||
|
|
assert key == "sk-file"
|
||
|
|
|
||
|
|
def test_file_skips_empty(self, tmp_path):
|
||
|
|
empty = tmp_path / "empty.txt"
|
||
|
|
empty.write_text("")
|
||
|
|
good = tmp_path / "good.txt"
|
||
|
|
good.write_text("sk-good")
|
||
|
|
key = resolve_api_key(
|
||
|
|
explicit=None,
|
||
|
|
env_var="NONEXISTENT_VAR_XYZ",
|
||
|
|
key_file_paths=[empty, good],
|
||
|
|
)
|
||
|
|
assert key == "sk-good"
|
||
|
|
|
||
|
|
def test_file_skips_missing(self, tmp_path):
|
||
|
|
missing = tmp_path / "no-such-file.txt"
|
||
|
|
key = resolve_api_key(
|
||
|
|
explicit=None,
|
||
|
|
env_var="NONEXISTENT_VAR_XYZ",
|
||
|
|
key_file_paths=[missing],
|
||
|
|
)
|
||
|
|
assert key is None
|
||
|
|
|
||
|
|
def test_returns_none_when_nothing_found(self):
|
||
|
|
key = resolve_api_key(
|
||
|
|
explicit=None,
|
||
|
|
env_var="NONEXISTENT_VAR_XYZ",
|
||
|
|
key_file_paths=[],
|
||
|
|
)
|
||
|
|
assert key is None
|
||
|
|
|
||
|
|
|
||
|
|
class TestFindProjectRoot:
|
||
|
|
def test_finds_root(self, tmp_path):
|
||
|
|
(tmp_path / "pyproject.toml").write_text("[project]")
|
||
|
|
sub = tmp_path / "a" / "b"
|
||
|
|
sub.mkdir(parents=True)
|
||
|
|
assert find_project_root(sub) == tmp_path
|
||
|
|
|
||
|
|
def test_returns_none_if_no_marker(self, tmp_path):
|
||
|
|
sub = tmp_path / "a" / "b"
|
||
|
|
sub.mkdir(parents=True)
|
||
|
|
# tmp_path itself won't have pyproject.toml
|
||
|
|
result = find_project_root(sub)
|
||
|
|
# Could be None or could find the real project root above;
|
||
|
|
# the important thing is it doesn't crash
|
||
|
|
assert result is None or (result / "pyproject.toml").exists()
|
||
|
|
|
||
|
|
|
||
|
|
class TestLoadConfig:
|
||
|
|
def test_returns_llmconfig(self):
|
||
|
|
cfg = load_config(provider="claude-code")
|
||
|
|
assert isinstance(cfg, LLMConfig)
|
||
|
|
assert cfg.provider == "claude-code"
|
||
|
|
|
||
|
|
def test_model_override(self):
|
||
|
|
cfg = load_config(model="my-model")
|
||
|
|
assert cfg.model == "my-model"
|
||
|
|
|
||
|
|
def test_explicit_key(self):
|
||
|
|
cfg = load_config(api_key="sk-test")
|
||
|
|
assert cfg.api_key == "sk-test"
|
||
|
|
|
||
|
|
def test_extra_overrides(self):
|
||
|
|
cfg = load_config(timeout_seconds=60)
|
||
|
|
assert cfg.timeout_seconds == 60
|