diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..00b3719 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,42 @@ +"""Tests for Settings field validators.""" + +import pytest +from pydantic import ValidationError + +from token0.config import Settings + + +def test_storage_mode_valid_values(): + assert Settings(storage_mode="lite").storage_mode == "lite" + assert Settings(storage_mode="full").storage_mode == "full" + + +def test_storage_mode_invalid_raises(): + with pytest.raises(ValidationError): + Settings(storage_mode="prod") + + +def test_text_density_threshold_valid(): + assert Settings(text_density_threshold=0.0).text_density_threshold == 0.0 + assert Settings(text_density_threshold=1.0).text_density_threshold == 1.0 + assert Settings(text_density_threshold=0.52).text_density_threshold == 0.52 + + +def test_text_density_threshold_out_of_range_raises(): + with pytest.raises(ValidationError): + Settings(text_density_threshold=1.1) + with pytest.raises(ValidationError): + Settings(text_density_threshold=-0.1) + + +def test_port_valid(): + assert Settings(port=8000).port == 8000 + assert Settings(port=1).port == 1 + assert Settings(port=65535).port == 65535 + + +def test_port_out_of_range_raises(): + with pytest.raises(ValidationError): + Settings(port=0) + with pytest.raises(ValidationError): + Settings(port=65536) diff --git a/token0/config.py b/token0/config.py index 7413ade..743cc01 100644 --- a/token0/config.py +++ b/token0/config.py @@ -1,10 +1,13 @@ +from typing import Literal + +from pydantic import field_validator from pydantic_settings import BaseSettings class Settings(BaseSettings): # Storage mode: "lite" (SQLite + in-memory) or "full" (Postgres + Redis + S3) # Use "lite" for local dev/testing, "full" for production - storage_mode: str = "lite" + storage_mode: Literal["lite", "full"] = "lite" # Database — only needed in full mode database_url: str = "postgresql+asyncpg://token0:token0@localhost:5432/token0" @@ -40,6 +43,20 @@ class Settings(BaseSettings): jpeg_quality: int = 85 text_density_threshold: float = 0.52 # Above this → OCR route instead of vision + @field_validator("text_density_threshold") + @classmethod + def validate_threshold(cls, v: float) -> float: + if not 0.0 <= v <= 1.0: + raise ValueError(f"text_density_threshold must be between 0.0 and 1.0, got {v}") + return v + + @field_validator("port") + @classmethod + def validate_port(cls, v: int) -> int: + if not 1 <= v <= 65535: + raise ValueError(f"port must be between 1 and 65535, got {v}") + return v + @property def is_lite(self) -> bool: return self.storage_mode == "lite"