Spaces:
Running
Running
| from __future__ import annotations | |
| from shopstack.config import Settings | |
| class TestSettings: | |
| def test_defaults(self, monkeypatch): | |
| monkeypatch.delenv("SHOPSTACK_OFF_THE_GRID", raising=False) | |
| s = Settings(_env_file=None) | |
| assert s.off_the_grid is True | |
| assert s.db_path | |
| assert s.app_port == 7860 | |
| def test_env_override(self, monkeypatch): | |
| monkeypatch.setenv("SHOPSTACK_DB_PATH", "/tmp/test.db") | |
| monkeypatch.setenv("SHOPSTACK_APP_PORT", "8080") | |
| monkeypatch.setenv("SHOPSTACK_OFF_THE_GRID", "false") | |
| s = Settings(_env_file=None) | |
| assert s.db_path == "/tmp/test.db" | |
| assert s.app_port == 8080 | |
| assert s.off_the_grid is False | |
| def test_cost_budget_default(self, monkeypatch): | |
| """Default budget is $1.00 USD per session.""" | |
| monkeypatch.delenv("SHOPSTACK_COST_BUDGET_LIMIT", raising=False) | |
| s = Settings(_env_file=None) | |
| assert s.cost_budget_limit == 1.00 | |
| def test_cost_budget_env_override(self, monkeypatch): | |
| """SHELLCK_COST_BUDGET_LIMIT env var overrides the default.""" | |
| monkeypatch.setenv("SHOPSTACK_COST_BUDGET_LIMIT", "5.50") | |
| s = Settings(_env_file=None) | |
| assert s.cost_budget_limit == 5.50 | |
| def test_cost_budget_field_exists(self, monkeypatch): | |
| """The cost_budget_limit field is required for planner/engine to read it. | |
| This regression-pins the fix: the field was added because | |
| planner/engine.py reads settings.cost_budget_limit, and removing | |
| the field would crash at import time. | |
| """ | |
| s = Settings(_env_file=None) | |
| assert hasattr(s, "cost_budget_limit") | |
| def test_provider_backends_default(self, monkeypatch): | |
| monkeypatch.delenv("SHOPSTACK_OFF_THE_GRID", raising=False) | |
| # conftest.py sets these to "mock" via os.environ.setdefault so the | |
| # session-scoped app import doesn't bootstrap heavy providers. Clear | |
| # them so we actually test the Settings defaults here. | |
| for var in ( | |
| "SHOPSTACK_PLANNER_BACKEND", "SHOPSTACK_STT_BACKEND", | |
| "SHOPSTACK_TTS_BACKEND", "SHOPSTACK_VISION_BACKEND", | |
| "SHOPSTACK_OCR_BACKEND", "SHOPSTACK_SEGMENTATION_BACKEND", | |
| "SHOPSTACK_EMBEDDINGS_BACKEND", | |
| ): | |
| monkeypatch.delenv(var, raising=False) | |
| s = Settings(_env_file=None) | |
| assert s.stt_backend == "sensevoice" | |
| assert s.tts_backend == "kokoro" | |
| assert s.vision_backend == "qwen3vl" | |
| assert s.segmentation_backend == "birefnet" | |
| assert s.promptable_segmentation_backend == "mobile_sam" | |
| assert s.planner_backend == "local" | |
| assert s.ocr_backend == "glm_ocr" | |
| def test_openbmb_model_stack_preset(self, monkeypatch): | |
| # Clear env vars that could interfere with preset application | |
| monkeypatch.delenv("SHOPSTACK_PLANNER_BACKEND", raising=False) | |
| monkeypatch.delenv("SHOPSTACK_VISION_BACKEND", raising=False) | |
| monkeypatch.delenv("SHOPSTACK_SEGMENTATION_BACKEND", raising=False) | |
| monkeypatch.delenv("SHOPSTACK_OCR_BACKEND", raising=False) | |
| monkeypatch.delenv("SHOPSTACK_EMBEDDINGS_BACKEND", raising=False) | |
| monkeypatch.delenv("SHOPSTACK_PROMPTABLE_SEGMENTATION_BACKEND", raising=False) | |
| monkeypatch.delenv("SHOPSTACK_STT_BACKEND", raising=False) | |
| monkeypatch.delenv("SHOPSTACK_TTS_BACKEND", raising=False) | |
| monkeypatch.delenv("SHOPSTACK_GROUNDING_BACKEND", raising=False) | |
| monkeypatch.delenv("SHOPSTACK_IMAGE_GEN_BACKEND", raising=False) | |
| monkeypatch.delenv("SHOPSTACK_TOOL_CALL_PARSER_BACKEND", raising=False) | |
| s = Settings(_env_file=None, model_stack="openbmb_local") | |
| assert s.planner_backend == "minicpm5" | |
| assert s.vision_backend == "qwen3vl" | |
| assert s.ocr_backend == "glm_ocr" | |
| # The openbmb_local preset only overrides planner + ocr; other | |
| # backends keep their defaults (nomic embeddings, minicpm5 parser, | |
| # sensevoice STT). Earlier revisions of the preset pinned more | |
| # fields, but those have since become the defaults. | |
| assert s.embeddings_backend == "nomic" | |
| # Phase 11 #1.7: was "minicpm5" but MiniCPM5Provider does not | |
| # declare ``tool_call_parser`` in its capabilities set. | |
| # The default is now "mock" so the registry resolves | |
| # immediately to MockToolCallParser. | |
| assert s.tool_call_parser_backend == "mock" | |
| assert s.stt_backend == "sensevoice" | |
| def test_provider_backends_compat_alias(self): | |
| s = Settings(_env_file=None, stt_backend="mock", tts_backend="mock", vision_backend="mock", ocr_backend="mock") | |
| assert s.provider_backends["stt"] == "mock" | |
| assert s.provider_backends["tts"] == "mock" | |
| assert s.provider_backends["vision"] == "mock" | |
| def test_database_path_alias(self, monkeypatch): | |
| monkeypatch.delenv("SHOPSTACK_OFF_THE_GRID", raising=False) | |
| s = Settings(_env_file=None, db_path="/tmp/legacy.db") | |
| assert s.database_path == "/tmp/legacy.db" | |
| def test_legacy_env_database_path_alias(self, monkeypatch): | |
| monkeypatch.setenv("SHOPSTACK_DATABASE_PATH", "/tmp/legacy-env.db") | |
| monkeypatch.delenv("SHOPSTACK_DB_PATH", raising=False) | |
| s = Settings(_env_file=None) | |
| assert s.db_path == "/tmp/legacy-env.db" | |