feat: add Pydantic Settings configuration
This commit is contained in:
@@ -23,3 +23,12 @@ dev = [
|
||||
[project.scripts]
|
||||
start = "uvicorn src.backend.main:app --host 0.0.0.0 --port 8000"
|
||||
dev = "uvicorn src.backend.main:app --reload --host 0.0.0.0 --port 8000"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=9.0.3",
|
||||
"pytest-asyncio>=1.3.0",
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
api_host: str = "0.0.0.0"
|
||||
api_port: int = 8000
|
||||
|
||||
api_key: str
|
||||
|
||||
rate_limit_per_minute: int = 10
|
||||
rate_limit_per_hour: int = 100
|
||||
|
||||
worker_url: str = "http://claude-worker:3001"
|
||||
|
||||
db_path: str = "/data/opus-agent.db"
|
||||
|
||||
claude_model: str = "claude-sonnet-4-6"
|
||||
|
||||
model_config = {
|
||||
"env_file": ".env",
|
||||
"env_file_encoding": "utf-8",
|
||||
}
|
||||
|
||||
|
||||
settings = Settings()
|
||||
@@ -0,0 +1,30 @@
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
_test_dir = tempfile.mkdtemp()
|
||||
|
||||
os.environ.setdefault("API_KEY", "test-key-12345")
|
||||
os.environ.setdefault("DB_PATH", os.path.join(_test_dir, "test.db"))
|
||||
os.environ.setdefault("WORKER_URL", "http://localhost:3001")
|
||||
os.environ.setdefault("CLAUDE_MODEL", "claude-sonnet-4-6")
|
||||
os.environ.setdefault("RATE_LIMIT_PER_MINUTE", "100")
|
||||
os.environ.setdefault("RATE_LIMIT_PER_HOUR", "1000")
|
||||
os.environ.setdefault("API_HOST", "0.0.0.0")
|
||||
os.environ.setdefault("API_PORT", "8000")
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_db():
|
||||
from src.backend.db.database import init_db, get_db
|
||||
|
||||
await init_db()
|
||||
yield
|
||||
db = await get_db()
|
||||
try:
|
||||
await db.execute("DELETE FROM requests")
|
||||
await db.execute("DELETE FROM topics")
|
||||
await db.commit()
|
||||
finally:
|
||||
await db.close()
|
||||
@@ -0,0 +1,111 @@
|
||||
from src.backend.config import settings
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_settings_loads_api_key():
|
||||
assert settings.api_key == "test-key-12345"
|
||||
|
||||
|
||||
def test_settings_loads_db_path():
|
||||
assert "test.db" in settings.db_path
|
||||
|
||||
|
||||
def test_settings_loads_worker_url():
|
||||
assert settings.worker_url == "http://localhost:3001"
|
||||
|
||||
|
||||
def test_settings_loads_claude_model():
|
||||
assert settings.claude_model == "claude-sonnet-4-6"
|
||||
|
||||
|
||||
def test_settings_loads_rate_limits():
|
||||
assert settings.rate_limit_per_minute == 100
|
||||
assert settings.rate_limit_per_hour == 1000
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_init_db_creates_tables():
|
||||
from src.backend.db.database import init_db, get_db
|
||||
|
||||
await init_db()
|
||||
db = await get_db()
|
||||
try:
|
||||
cursor = await db.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
||||
)
|
||||
tables = [row[0] for row in await cursor.fetchall()]
|
||||
assert "topics" in tables
|
||||
assert "requests" in tables
|
||||
finally:
|
||||
await db.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_foreign_keys_enabled():
|
||||
from src.backend.db.database import init_db, get_db
|
||||
|
||||
await init_db()
|
||||
db = await get_db()
|
||||
try:
|
||||
cursor = await db.execute("PRAGMA foreign_keys")
|
||||
row = await cursor.fetchone()
|
||||
assert row[0] == 1
|
||||
finally:
|
||||
await db.close()
|
||||
|
||||
|
||||
def test_topic_create_model_validates():
|
||||
from src.backend.models.api import TopicCreate
|
||||
|
||||
topic = TopicCreate(
|
||||
name="coding",
|
||||
system_prompt="You are an expert coder.",
|
||||
summary="Coding questions",
|
||||
model="claude-sonnet-4-6",
|
||||
)
|
||||
assert topic.name == "coding"
|
||||
assert topic.model == "claude-sonnet-4-6"
|
||||
|
||||
|
||||
def test_topic_create_model_optional_fields():
|
||||
from src.backend.models.api import TopicCreate
|
||||
|
||||
topic = TopicCreate(name="coding", system_prompt="You are an expert coder.")
|
||||
assert topic.summary == ""
|
||||
assert topic.model is None
|
||||
|
||||
|
||||
def test_request_create_model_validates():
|
||||
from src.backend.models.api import RequestCreate
|
||||
|
||||
req = RequestCreate(
|
||||
prompt="How to do X?",
|
||||
topic_id="550e8400-e29b-41d4-a716-446655440000",
|
||||
)
|
||||
assert req.session_id is None
|
||||
assert req.model is None
|
||||
assert req.summarize is False
|
||||
|
||||
|
||||
def test_model_validation_rejects_invalid():
|
||||
from src.backend.models.api import TopicCreate
|
||||
from pydantic import ValidationError
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
TopicCreate(name="test", system_prompt="test", model="gpt-4")
|
||||
|
||||
|
||||
def test_envelope_ok():
|
||||
from src.backend.models.api import ok
|
||||
|
||||
result = ok({"key": "value"})
|
||||
assert result == {"success": True, "data": {"key": "value"}, "error": None}
|
||||
|
||||
|
||||
def test_envelope_fail():
|
||||
from src.backend.models.api import fail
|
||||
|
||||
result = fail("NOT_FOUND", "Resource not found")
|
||||
assert result["success"] is False
|
||||
assert result["error"]["code"] == "NOT_FOUND"
|
||||
Reference in New Issue
Block a user