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
238 lines
8.6 KiB
Python
238 lines
8.6 KiB
Python
"""
|
|
Tests for AdaptiveRateLimitTracker.
|
|
"""
|
|
|
|
from unittest.mock import patch
|
|
|
|
from local_deep_research.web_search_engines.rate_limiting.tracker import (
|
|
AdaptiveRateLimitTracker,
|
|
)
|
|
|
|
|
|
class TestAdaptiveRateLimitTrackerInit:
|
|
"""Tests for AdaptiveRateLimitTracker initialization."""
|
|
|
|
def test_init_with_default_settings(self):
|
|
"""Initializes with default settings."""
|
|
with patch(
|
|
"local_deep_research.web_search_engines.rate_limiting.tracker.get_setting_from_snapshot"
|
|
) as mock_get:
|
|
from local_deep_research.config.thread_settings import (
|
|
NoSettingsContextError,
|
|
)
|
|
|
|
mock_get.side_effect = NoSettingsContextError()
|
|
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
|
|
# Should use defaults when settings are not available
|
|
assert tracker.memory_window == 100
|
|
assert tracker.exploration_rate == 0.1
|
|
assert tracker.learning_rate == 0.3
|
|
assert tracker.decay_per_day == 0.95
|
|
|
|
def test_init_programmatic_mode(self):
|
|
"""Initializes in programmatic mode."""
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
assert tracker.programmatic_mode is True
|
|
|
|
def test_init_with_settings_snapshot(self, mock_settings_snapshot):
|
|
"""Initializes with settings snapshot."""
|
|
tracker = AdaptiveRateLimitTracker(
|
|
settings_snapshot=mock_settings_snapshot,
|
|
programmatic_mode=True,
|
|
)
|
|
assert tracker.settings_snapshot == mock_settings_snapshot
|
|
|
|
def test_init_empty_caches(self):
|
|
"""Initializes with empty caches."""
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
assert tracker.recent_attempts == {}
|
|
assert tracker.current_estimates == {}
|
|
|
|
|
|
class TestAdaptiveRateLimitTrackerProfiles:
|
|
"""Tests for rate limiting profiles."""
|
|
|
|
def test_balanced_profile(self):
|
|
"""Balanced profile keeps default values."""
|
|
with patch(
|
|
"local_deep_research.web_search_engines.rate_limiting.tracker.get_setting_from_snapshot"
|
|
) as mock_get:
|
|
from local_deep_research.config.thread_settings import (
|
|
NoSettingsContextError,
|
|
)
|
|
|
|
mock_get.side_effect = NoSettingsContextError()
|
|
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
tracker._apply_profile("balanced")
|
|
|
|
# Values should be unchanged from defaults
|
|
assert tracker.exploration_rate == 0.1
|
|
assert tracker.learning_rate == 0.3
|
|
|
|
def test_conservative_profile(self):
|
|
"""Conservative profile reduces exploration and learning rates."""
|
|
with patch(
|
|
"local_deep_research.web_search_engines.rate_limiting.tracker.get_setting_from_snapshot"
|
|
) as mock_get:
|
|
from local_deep_research.config.thread_settings import (
|
|
NoSettingsContextError,
|
|
)
|
|
|
|
mock_get.side_effect = NoSettingsContextError()
|
|
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
original_exploration = tracker.exploration_rate
|
|
original_learning = tracker.learning_rate
|
|
|
|
tracker._apply_profile("conservative")
|
|
|
|
# Values should be reduced
|
|
assert tracker.exploration_rate < original_exploration
|
|
assert tracker.learning_rate < original_learning
|
|
|
|
def test_aggressive_profile(self):
|
|
"""Aggressive profile increases exploration and learning rates."""
|
|
with patch(
|
|
"local_deep_research.web_search_engines.rate_limiting.tracker.get_setting_from_snapshot"
|
|
) as mock_get:
|
|
from local_deep_research.config.thread_settings import (
|
|
NoSettingsContextError,
|
|
)
|
|
|
|
mock_get.side_effect = NoSettingsContextError()
|
|
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
original_exploration = tracker.exploration_rate
|
|
original_learning = tracker.learning_rate
|
|
|
|
tracker._apply_profile("aggressive")
|
|
|
|
# Values should be increased
|
|
assert tracker.exploration_rate > original_exploration
|
|
assert tracker.learning_rate > original_learning
|
|
|
|
|
|
class TestAdaptiveRateLimitTrackerEnabled:
|
|
"""Tests for enabled/disabled state."""
|
|
|
|
def test_enabled_by_default_in_normal_mode(self):
|
|
"""Enabled by default in normal mode."""
|
|
with patch(
|
|
"local_deep_research.web_search_engines.rate_limiting.tracker.get_setting_from_snapshot"
|
|
) as mock_get:
|
|
from local_deep_research.config.thread_settings import (
|
|
NoSettingsContextError,
|
|
)
|
|
|
|
mock_get.side_effect = NoSettingsContextError()
|
|
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=False)
|
|
assert tracker.enabled is True
|
|
|
|
def test_disabled_by_default_in_programmatic_mode(self):
|
|
"""Disabled by default in programmatic mode."""
|
|
with patch(
|
|
"local_deep_research.web_search_engines.rate_limiting.tracker.get_setting_from_snapshot"
|
|
) as mock_get:
|
|
from local_deep_research.config.thread_settings import (
|
|
NoSettingsContextError,
|
|
)
|
|
|
|
mock_get.side_effect = NoSettingsContextError()
|
|
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
assert tracker.enabled is False
|
|
|
|
|
|
class TestAdaptiveRateLimitTrackerMemory:
|
|
"""Tests for in-memory tracking."""
|
|
|
|
def test_recent_attempts_stored(self):
|
|
"""Recent attempts are stored in memory."""
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
|
|
# Manually add some attempts
|
|
from collections import deque
|
|
|
|
tracker.recent_attempts["test_engine"] = deque(maxlen=100)
|
|
tracker.recent_attempts["test_engine"].append(
|
|
{
|
|
"wait_time": 1.0,
|
|
"success": True,
|
|
"timestamp": 1234567890,
|
|
}
|
|
)
|
|
|
|
assert len(tracker.recent_attempts["test_engine"]) == 1
|
|
|
|
def test_estimates_stored(self):
|
|
"""Estimates are stored in memory."""
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
|
|
tracker.current_estimates["test_engine"] = {
|
|
"min_wait": 0.5,
|
|
"max_wait": 2.0,
|
|
"avg_wait": 1.0,
|
|
}
|
|
|
|
assert "test_engine" in tracker.current_estimates
|
|
assert tracker.current_estimates["test_engine"]["avg_wait"] == 1.0
|
|
|
|
|
|
class TestAdaptiveRateLimitTrackerDatabaseSkip:
|
|
"""Tests for database operation skipping."""
|
|
|
|
def test_load_estimates_skipped_in_programmatic_mode(self):
|
|
"""_load_estimates skips database in programmatic mode."""
|
|
with patch(
|
|
"local_deep_research.web_search_engines.rate_limiting.tracker._get_db_imports"
|
|
):
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
|
|
# Should not attempt to get database imports
|
|
assert tracker._estimates_loaded is True
|
|
|
|
def test_load_estimates_deferred_in_non_programmatic_mode(self):
|
|
"""_load_estimates defers loading in non-programmatic mode."""
|
|
with patch(
|
|
"local_deep_research.web_search_engines.rate_limiting.tracker.get_setting_from_snapshot"
|
|
) as mock_get:
|
|
from local_deep_research.config.thread_settings import (
|
|
NoSettingsContextError,
|
|
)
|
|
|
|
mock_get.side_effect = NoSettingsContextError()
|
|
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=False)
|
|
|
|
# Non-programmatic defers loading until user context is available
|
|
assert tracker._estimates_loaded is False
|
|
assert tracker.current_estimates == {}
|
|
|
|
|
|
class TestRateLimitTrackerConstants:
|
|
"""Tests for tracker constants and settings."""
|
|
|
|
def test_memory_window_positive(self):
|
|
"""Memory window is positive."""
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
assert tracker.memory_window > 0
|
|
|
|
def test_exploration_rate_valid_range(self):
|
|
"""Exploration rate is between 0 and 1."""
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
assert 0 <= tracker.exploration_rate <= 1
|
|
|
|
def test_learning_rate_valid_range(self):
|
|
"""Learning rate is between 0 and 1."""
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
assert 0 <= tracker.learning_rate <= 1
|
|
|
|
def test_decay_per_day_valid_range(self):
|
|
"""Decay per day is between 0 and 1."""
|
|
tracker = AdaptiveRateLimitTracker(programmatic_mode=True)
|
|
assert 0 <= tracker.decay_per_day <= 1
|