Files
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

423 lines
13 KiB
Python

"""Tests for settings and API key database models."""
from datetime import datetime, timezone
import pytest
from sqlalchemy import create_engine
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import sessionmaker
from local_deep_research.database.models import (
APIKey,
Base,
Setting,
SettingType,
UserSettings,
)
class TestSettingsModels:
"""Test suite for settings-related models."""
@pytest.fixture
def engine(self):
"""Create an in-memory SQLite database for testing."""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
yield engine
engine.dispose()
@pytest.fixture
def session(self, engine):
"""Create a database session for testing."""
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
def test_setting_creation(self, session):
"""Test creating various types of settings."""
# App setting
app_setting = Setting(
key="app.theme",
value={"mode": "dark", "accent": "blue"},
type=SettingType.APP,
name="Theme",
category="appearance",
description="Application theme settings",
ui_element="select",
options=["light", "dark", "auto"],
visible=True,
editable=True,
)
# LLM setting
llm_setting = Setting(
key="llm.temperature",
value=0.7,
type=SettingType.LLM,
name="Temperature",
category="model",
description="LLM response temperature",
ui_element="slider",
min_value=0.0,
max_value=2.0,
step=0.1,
visible=True,
editable=True,
)
# Search setting
search_setting = Setting(
key="search.max_results",
value=20,
type=SettingType.SEARCH,
name="Max Results",
category="search",
description="Maximum search results per query",
ui_element="number",
min_value=1,
max_value=100,
visible=True,
editable=True,
)
# Report setting
report_setting = Setting(
key="report.format",
value="markdown",
type=SettingType.REPORT,
name="Report Format",
category="output",
description="Default report format",
ui_element="select",
options=["markdown", "html", "pdf"],
visible=True,
editable=True,
)
session.add_all(
[app_setting, llm_setting, search_setting, report_setting]
)
session.commit()
# Verify settings
all_settings = session.query(Setting).all()
assert len(all_settings) == 4
llm = session.query(Setting).filter_by(key="llm.temperature").first()
assert llm.value == 0.7
assert llm.type == SettingType.LLM
assert llm.min_value == 0.0
assert llm.max_value == 2.0
def test_unique_key_constraint(self, session):
"""Test that setting keys must be unique."""
setting1 = Setting(
key="api.timeout",
value=30,
type=SettingType.APP,
name="API Timeout",
category="api",
)
setting2 = Setting(
key="api.timeout", # Duplicate key
value=60,
type=SettingType.APP,
name="API Timeout",
category="api",
)
session.add(setting1)
session.commit()
session.add(setting2)
with pytest.raises(IntegrityError):
session.commit()
def test_secret_settings(self, session):
"""Test handling of secret settings (though Setting model doesn't have is_secret)."""
# Using APIKey for secrets instead
api_key = APIKey(
provider="openai",
key="sk-abc123...", # Would be encrypted by SQLCipher
description="OpenAI API key for GPT models",
is_active=True,
)
session.add(api_key)
session.commit()
# Verify API key
saved = session.query(APIKey).filter_by(provider="openai").first()
assert saved is not None
assert saved.is_active is True
assert saved.key == "sk-abc123..."
# In real usage, the key would be encrypted by SQLCipher
def test_system_settings(self, session):
"""Test system-wide settings that shouldn't be user-editable."""
system_setting = Setting(
key="system.version",
value="1.0.0",
type=SettingType.APP,
name="System Version",
category="system",
description="Current system version",
visible=True,
editable=False, # System setting - not editable
)
session.add(system_setting)
session.commit()
saved = session.query(Setting).filter_by(key="system.version").first()
assert saved.editable is False
assert saved.visible is True
def test_user_settings(self, session):
"""Test UserSettings for user-specific preferences."""
# User preferences
preferences = [
UserSettings(
key="preferred_model",
value={"provider": "openai", "model": "gpt-4"},
category="preferences",
description="User's preferred LLM model",
),
UserSettings(
key="search_history_enabled",
value=True,
category="privacy",
description="Whether to save search history",
),
UserSettings(
key="ui_language",
value="en",
category="localization",
description="User interface language",
),
]
session.add_all(preferences)
session.commit()
# Query by category
privacy_settings = (
session.query(UserSettings).filter_by(category="privacy").all()
)
assert len(privacy_settings) == 1
assert privacy_settings[0].value is True
def test_api_key_management(self, session):
"""Test APIKey model for secure API key storage."""
# Add multiple API keys
keys = [
APIKey(
provider="openai",
key="sk-openai...",
description="OpenAI GPT models",
is_active=True,
),
APIKey(
provider="anthropic",
key="sk-ant...",
description="Claude models",
is_active=True,
),
APIKey(
provider="google",
key="AIza...",
description="Google search API",
is_active=False, # Disabled
),
]
session.add_all(keys)
session.commit()
# Query active keys
active_keys = session.query(APIKey).filter_by(is_active=True).all()
assert len(active_keys) == 2
# Test unique provider constraint
duplicate = APIKey(
provider="openai", # Already exists
key="sk-different...",
description="Duplicate provider",
)
session.add(duplicate)
with pytest.raises(IntegrityError):
session.commit()
def test_setting_categories(self, session):
"""Test organizing settings by category."""
categories = ["appearance", "performance", "security", "advanced"]
for i, cat in enumerate(categories):
for j in range(3):
setting = Setting(
key=f"{cat}.setting_{j}",
value=f"value_{j}",
type=SettingType.APP,
name=f"{cat.title()} Setting {j}",
category=cat,
visible=True if i < 3 else False, # Hide advanced
)
session.add(setting)
session.commit()
# Query by category
appearance = (
session.query(Setting).filter_by(category="appearance").all()
)
assert len(appearance) == 3
# Query visible settings
visible = session.query(Setting).filter_by(visible=True).count()
assert visible == 9 # 3 categories * 3 settings each
def test_setting_value_types(self, session):
"""Test different value types stored in JSON columns."""
settings = [
UserSettings(key="string_val", value="text value"),
UserSettings(key="int_val", value=42),
UserSettings(key="float_val", value=3.14),
UserSettings(key="bool_val", value=True),
UserSettings(key="list_val", value=[1, 2, 3]),
UserSettings(key="dict_val", value={"a": 1, "b": 2}),
UserSettings(key="null_val", value=None),
]
session.add_all(settings)
session.commit()
# Verify different types
assert (
session.query(UserSettings)
.filter_by(key="string_val")
.first()
.value
== "text value"
)
assert (
session.query(UserSettings).filter_by(key="int_val").first().value
== 42
)
assert (
session.query(UserSettings).filter_by(key="float_val").first().value
== 3.14
)
assert (
session.query(UserSettings).filter_by(key="bool_val").first().value
is True
)
assert session.query(UserSettings).filter_by(
key="list_val"
).first().value == [1, 2, 3]
assert session.query(UserSettings).filter_by(
key="dict_val"
).first().value == {"a": 1, "b": 2}
assert (
session.query(UserSettings).filter_by(key="null_val").first().value
is None
)
def test_api_key_rotation(self, session):
"""Test API key rotation and usage tracking."""
# Create initial key
api_key = APIKey(
provider="openai",
key="sk-old...",
description="OpenAI API key",
is_active=True,
usage_count=100,
)
session.add(api_key)
session.commit()
# Simulate key usage
api_key.usage_count += 1
api_key.last_used = datetime.now(timezone.utc)
session.commit()
# Rotate key (deactivate old, add new)
api_key.is_active = False
new_key = APIKey(
provider="openai_new", # Different provider name to avoid constraint
key="sk-new...",
description="OpenAI API key (rotated)",
is_active=True,
)
session.add(new_key)
session.commit()
# Verify rotation
old_key = session.query(APIKey).filter_by(provider="openai").first()
assert old_key.is_active is False
assert old_key.usage_count == 101
active_key = (
session.query(APIKey)
.filter_by(is_active=True, provider="openai_new")
.first()
)
assert active_key.key == "sk-new..."
def test_user_settings_defaults(self, session):
"""Test default values for user settings."""
# Get a non-existent setting
setting = (
session.query(UserSettings)
.filter_by(key="non_existent_setting")
.first()
)
assert setting is None
# Create setting with defaults
default_setting = UserSettings(
key="new_feature_enabled",
value=False, # Default to disabled
category="features",
description="New experimental feature",
)
session.add(default_setting)
session.commit()
saved = (
session.query(UserSettings)
.filter_by(key="new_feature_enabled")
.first()
)
assert saved.value is False
assert saved.created_at is not None
assert saved.updated_at is not None
def test_setting_update_tracking(self, session):
"""Test that updated_at is properly tracked."""
setting = Setting(
key="test.update",
value="initial",
type=SettingType.APP,
name="Test Update",
category="test",
)
session.add(setting)
session.commit()
# Update the setting
setting.value = "updated"
session.commit()
# Note: onupdate might not trigger in SQLite without proper configuration
# In production with PostgreSQL, this would work automatically