7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
156 lines
5.5 KiB
Python
156 lines
5.5 KiB
Python
"""Tests for validate_setting() edge cases.
|
|
|
|
Covers:
|
|
- validate_setting() min/max constraints, select edge cases, unknown ui_element (12 tests)
|
|
"""
|
|
|
|
from unittest.mock import patch
|
|
|
|
from local_deep_research.web.routes.settings_routes import (
|
|
validate_setting,
|
|
DYNAMIC_SETTINGS,
|
|
)
|
|
from local_deep_research.web.models.settings import BaseSetting, SettingType
|
|
|
|
|
|
# ── validate_setting() edge case tests ──────────────────────────────
|
|
|
|
|
|
def _make_setting(ui_element, **kwargs):
|
|
"""Helper to build a BaseSetting with minimal boilerplate."""
|
|
defaults = dict(
|
|
key="test.key",
|
|
value=0,
|
|
type=SettingType.APP,
|
|
name="Test",
|
|
)
|
|
defaults.update(kwargs)
|
|
defaults["ui_element"] = ui_element
|
|
return BaseSetting(**defaults)
|
|
|
|
|
|
class TestValidateSettingMinMax:
|
|
"""Tests for number/slider/range min/max constraint validation."""
|
|
|
|
def test_number_below_min(self):
|
|
"""Number below min_value → (False, error)."""
|
|
setting = _make_setting("number", min_value=0)
|
|
valid, msg = validate_setting(setting, -1)
|
|
assert valid is False
|
|
assert "at least" in msg
|
|
|
|
def test_number_above_max(self):
|
|
"""Number above max_value → (False, error)."""
|
|
setting = _make_setting("number", max_value=100)
|
|
valid, msg = validate_setting(setting, 150)
|
|
assert valid is False
|
|
assert "at most" in msg
|
|
|
|
def test_number_no_constraints(self):
|
|
"""Number with no min/max → (True, None)."""
|
|
setting = _make_setting("number")
|
|
valid, msg = validate_setting(setting, 999)
|
|
assert valid is True
|
|
assert msg is None
|
|
|
|
def test_number_only_min_passes(self):
|
|
"""Number with only min_value, value above → (True, None)."""
|
|
setting = _make_setting("number", min_value=0)
|
|
valid, msg = validate_setting(setting, 5)
|
|
assert valid is True
|
|
|
|
def test_number_only_max_passes(self):
|
|
"""Number with only max_value, value below → (True, None)."""
|
|
setting = _make_setting("number", max_value=100)
|
|
valid, msg = validate_setting(setting, 50)
|
|
assert valid is True
|
|
|
|
def test_slider_below_min(self):
|
|
"""Slider below min_value → (False, error).
|
|
|
|
'slider' is not in UI_ELEMENT_TO_SETTING_TYPE, so get_typed_setting_value
|
|
returns default=None. We patch it to pass through the numeric value so
|
|
we can test the min/max constraint logic in validate_setting.
|
|
"""
|
|
setting = _make_setting("slider", min_value=5)
|
|
with patch(
|
|
"local_deep_research.web.routes.settings_routes.get_typed_setting_value",
|
|
return_value=2,
|
|
):
|
|
valid, msg = validate_setting(setting, 2)
|
|
assert valid is False
|
|
assert "at least" in msg
|
|
|
|
def test_range_above_max(self):
|
|
"""Range above max_value → (False, error)."""
|
|
setting = _make_setting("range", max_value=10)
|
|
valid, msg = validate_setting(setting, 15)
|
|
assert valid is False
|
|
assert "at most" in msg
|
|
|
|
|
|
class TestValidateSettingSelect:
|
|
"""Tests for select ui_element edge cases."""
|
|
|
|
def test_select_invalid_option(self):
|
|
"""Select with value not in options → (False, error)."""
|
|
setting = _make_setting(
|
|
"select",
|
|
options=[{"value": "a"}, {"value": "b"}],
|
|
)
|
|
valid, msg = validate_setting(setting, "x")
|
|
assert valid is False
|
|
assert "must be one of" in msg
|
|
|
|
def test_select_plain_string_options(self):
|
|
"""Select with plain string options list → valid option passes.
|
|
|
|
The Pydantic model enforces List[Dict], but at runtime the
|
|
validate_setting code handles both dict and non-dict options
|
|
(line 199). We use a mock to bypass Pydantic validation.
|
|
"""
|
|
from unittest.mock import MagicMock
|
|
|
|
setting = MagicMock()
|
|
setting.key = "test.select"
|
|
setting.ui_element = "select"
|
|
setting.options = ["a", "b", "c"]
|
|
|
|
# Patch get_typed_setting_value to pass through the value unchanged
|
|
with patch(
|
|
"local_deep_research.web.routes.settings_routes.get_typed_setting_value",
|
|
return_value="b",
|
|
):
|
|
valid, msg = validate_setting(setting, "b")
|
|
assert valid is True
|
|
|
|
def test_select_empty_options_skips_validation(self):
|
|
"""Select with empty options list → validation skipped → (True, None)."""
|
|
setting = _make_setting("select", options=[])
|
|
valid, msg = validate_setting(setting, "anything")
|
|
assert valid is True
|
|
|
|
def test_select_dynamic_setting_skips_validation(self):
|
|
"""Select with key in DYNAMIC_SETTINGS → validation skipped."""
|
|
# Use a real DYNAMIC_SETTINGS key
|
|
dynamic_key = DYNAMIC_SETTINGS[0] # "llm.provider"
|
|
setting = _make_setting(
|
|
"select",
|
|
key=dynamic_key,
|
|
options=[{"value": "a"}, {"value": "b"}],
|
|
)
|
|
# Value not in options, but should pass because it's dynamic
|
|
valid, msg = validate_setting(setting, "nonexistent_provider")
|
|
assert valid is True
|
|
|
|
|
|
class TestValidateSettingUnknownElement:
|
|
"""Test that unknown ui_element types pass through."""
|
|
|
|
def test_unknown_ui_element_passes(self):
|
|
"""Unknown ui_element like 'custom_widget' → (True, None)."""
|
|
setting = _make_setting("custom_widget")
|
|
valid, msg = validate_setting(setting, "any value")
|
|
assert valid is True
|
|
assert msg is None
|