fix(backend): config values (#12944)

This commit is contained in:
sp.wack
2026-02-20 17:53:35 +04:00
committed by GitHub
parent 32ab2a24c6
commit 3bc2ef954e
2 changed files with 566 additions and 6 deletions

View File

@@ -19,12 +19,104 @@ def _get_recaptcha_site_key() -> str | None:
return key if key else None
# OSS default PostHog key - used when no environment variable is configured
_OSS_POSTHOG_KEY = 'phc_3ESMmY9SgqEAGBB6sMGK5ayYHkeUuknH2vP6FmWH9RA'
def _get_posthog_client_key() -> str:
"""Get PostHog client key from environment variable.
Reads POSTHOG_CLIENT_KEY from environment. If not set or empty,
returns the OSS default key for backwards compatibility.
"""
key = os.getenv('POSTHOG_CLIENT_KEY', '').strip()
return key if key else _OSS_POSTHOG_KEY
def _get_auth_url() -> str | None:
"""Get authentication service URL from environment variable.
Reads AUTH_URL from environment. If not set or empty, returns None.
"""
url = os.getenv('AUTH_URL', '').strip()
return url if url else None
def _get_maintenance_start_time() -> datetime | None:
"""Get maintenance start time from environment variable.
Reads MAINTENANCE_START_TIME from environment. If set to a valid ISO 8601
timestamp, returns the parsed datetime. If empty, unset, or invalid,
returns None (graceful fallback).
"""
value = os.getenv('MAINTENANCE_START_TIME', '').strip()
if not value:
return None
try:
return datetime.fromisoformat(value)
except ValueError:
return None
def _get_providers_configured() -> list[ProviderType]:
"""Get configured OAuth providers from environment variables.
Checks for presence of OAuth client ID env vars and returns a list of
configured providers. Mirrors legacy logic from SaaSServerConfig.
"""
providers: list[ProviderType] = []
if os.getenv('GITHUB_APP_CLIENT_ID', '').strip():
providers.append(ProviderType.GITHUB)
if os.getenv('GITLAB_APP_CLIENT_ID', '').strip():
providers.append(ProviderType.GITLAB)
if os.getenv('BITBUCKET_APP_CLIENT_ID', '').strip():
providers.append(ProviderType.BITBUCKET)
if os.getenv('ENABLE_ENTERPRISE_SSO', '').strip():
providers.append(ProviderType.ENTERPRISE_SSO)
return providers
def _get_github_app_slug() -> str | None:
"""Get GitHub app slug from environment variable.
Reads GITHUB_APP_SLUG from environment. If set, returns the value.
If empty or unset, returns None.
"""
slug = os.getenv('GITHUB_APP_SLUG', '').strip()
return slug if slug else None
def _get_feature_flags() -> WebClientFeatureFlags:
"""Get feature flags from environment variables.
Reads ENABLE_BILLING, HIDE_LLM_SETTINGS, ENABLE_JIRA, ENABLE_JIRA_DC,
and ENABLE_LINEAR from environment. Each flag is True only if the
corresponding env var is exactly 'true', otherwise False.
"""
return WebClientFeatureFlags(
enable_billing=os.getenv('ENABLE_BILLING', 'false') == 'true',
hide_llm_settings=os.getenv('HIDE_LLM_SETTINGS', 'false') == 'true',
enable_jira=os.getenv('ENABLE_JIRA', 'false') == 'true',
enable_jira_dc=os.getenv('ENABLE_JIRA_DC', 'false') == 'true',
enable_linear=os.getenv('ENABLE_LINEAR', 'false') == 'true',
)
class DefaultWebClientConfigInjector(WebClientConfigInjector):
posthog_client_key: str | None = 'phc_3ESMmY9SgqEAGBB6sMGK5ayYHkeUuknH2vP6FmWH9RA'
feature_flags: WebClientFeatureFlags = Field(default_factory=WebClientFeatureFlags)
providers_configured: list[ProviderType] = Field(default_factory=list)
maintenance_start_time: datetime | None = None
auth_url: str | None = None
posthog_client_key: str = Field(default_factory=_get_posthog_client_key)
feature_flags: WebClientFeatureFlags = Field(default_factory=_get_feature_flags)
providers_configured: list[ProviderType] = Field(
default_factory=_get_providers_configured
)
maintenance_start_time: datetime | None = Field(
default_factory=_get_maintenance_start_time
)
auth_url: str | None = Field(default_factory=_get_auth_url)
recaptcha_site_key: str | None = Field(default_factory=_get_recaptcha_site_key)
faulty_models: list[str] = Field(default_factory=list)
error_message: str | None = None
@@ -36,7 +128,7 @@ class DefaultWebClientConfigInjector(WebClientConfigInjector):
'new and should be displayed. (Default to start of 2026)'
),
)
github_app_slug: str | None = None
github_app_slug: str | None = Field(default_factory=_get_github_app_slug)
async def get_web_client_config(self) -> WebClientConfig:
from openhands.app_server.config import get_global_config

View File

@@ -0,0 +1,468 @@
"""Tests for DefaultWebClientConfigInjector.
This module tests environment variable handling in DefaultWebClientConfigInjector.
"""
import os
from unittest.mock import patch
class TestGetPosthogClientKey:
"""Test cases for _get_posthog_client_key helper function."""
OSS_DEFAULT_KEY = 'phc_3ESMmY9SgqEAGBB6sMGK5ayYHkeUuknH2vP6FmWH9RA'
def test_returns_env_var_when_set(self):
"""When POSTHOG_CLIENT_KEY is set, return that value."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_posthog_client_key,
)
with patch.dict(os.environ, {'POSTHOG_CLIENT_KEY': 'phc_saas_key_123'}):
result = _get_posthog_client_key()
assert result == 'phc_saas_key_123'
def test_returns_oss_default_when_env_var_unset(self):
"""When POSTHOG_CLIENT_KEY is not set, return the OSS default key."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_posthog_client_key,
)
with patch.dict(os.environ, {}, clear=True):
# Ensure POSTHOG_CLIENT_KEY is not in environment
os.environ.pop('POSTHOG_CLIENT_KEY', None)
result = _get_posthog_client_key()
assert result == self.OSS_DEFAULT_KEY
def test_returns_oss_default_when_env_var_empty(self):
"""When POSTHOG_CLIENT_KEY is empty string, return the OSS default key."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_posthog_client_key,
)
with patch.dict(os.environ, {'POSTHOG_CLIENT_KEY': ''}):
result = _get_posthog_client_key()
assert result == self.OSS_DEFAULT_KEY
def test_strips_whitespace_from_env_var(self):
"""When POSTHOG_CLIENT_KEY has whitespace, strip it."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_posthog_client_key,
)
with patch.dict(os.environ, {'POSTHOG_CLIENT_KEY': ' phc_trimmed_key '}):
result = _get_posthog_client_key()
assert result == 'phc_trimmed_key'
def test_returns_oss_default_when_env_var_only_whitespace(self):
"""When POSTHOG_CLIENT_KEY is only whitespace, return the OSS default key."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_posthog_client_key,
)
with patch.dict(os.environ, {'POSTHOG_CLIENT_KEY': ' '}):
result = _get_posthog_client_key()
assert result == self.OSS_DEFAULT_KEY
class TestGetAuthUrl:
"""Test cases for _get_auth_url helper function."""
def test_returns_env_var_when_set(self):
"""When AUTH_URL is set, return that value."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_auth_url,
)
with patch.dict(os.environ, {'AUTH_URL': 'https://auth.example.com'}):
result = _get_auth_url()
assert result == 'https://auth.example.com'
def test_returns_none_when_env_var_unset(self):
"""When AUTH_URL is not set, return None."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_auth_url,
)
with patch.dict(os.environ, {}, clear=True):
os.environ.pop('AUTH_URL', None)
result = _get_auth_url()
assert result is None
def test_returns_none_when_env_var_empty(self):
"""When AUTH_URL is empty string, return None."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_auth_url,
)
with patch.dict(os.environ, {'AUTH_URL': ''}):
result = _get_auth_url()
assert result is None
def test_strips_whitespace_from_env_var(self):
"""When AUTH_URL has whitespace, strip it."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_auth_url,
)
with patch.dict(os.environ, {'AUTH_URL': ' https://auth.example.com '}):
result = _get_auth_url()
assert result == 'https://auth.example.com'
def test_returns_none_when_env_var_only_whitespace(self):
"""When AUTH_URL is only whitespace, return None."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_auth_url,
)
with patch.dict(os.environ, {'AUTH_URL': ' '}):
result = _get_auth_url()
assert result is None
class TestGetFeatureFlags:
"""Test cases for _get_feature_flags helper function."""
def test_returns_all_false_when_no_env_vars_set(self):
"""When no feature flag env vars are set, all flags default to False."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_feature_flags,
)
with patch.dict(os.environ, {}, clear=True):
# Remove any existing feature flag env vars
for var in [
'ENABLE_BILLING',
'HIDE_LLM_SETTINGS',
'ENABLE_JIRA',
'ENABLE_JIRA_DC',
'ENABLE_LINEAR',
]:
os.environ.pop(var, None)
result = _get_feature_flags()
assert result.enable_billing is False
assert result.hide_llm_settings is False
assert result.enable_jira is False
assert result.enable_jira_dc is False
assert result.enable_linear is False
def test_enable_billing_true_when_env_var_true(self):
"""When ENABLE_BILLING is 'true', enable_billing flag is True."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_feature_flags,
)
with patch.dict(os.environ, {'ENABLE_BILLING': 'true'}):
result = _get_feature_flags()
assert result.enable_billing is True
def test_enable_billing_false_when_env_var_false(self):
"""When ENABLE_BILLING is 'false', enable_billing flag is False."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_feature_flags,
)
with patch.dict(os.environ, {'ENABLE_BILLING': 'false'}):
result = _get_feature_flags()
assert result.enable_billing is False
def test_enable_billing_false_when_env_var_other_value(self):
"""When ENABLE_BILLING is any value other than 'true', enable_billing is False."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_feature_flags,
)
with patch.dict(os.environ, {'ENABLE_BILLING': 'yes'}):
result = _get_feature_flags()
assert result.enable_billing is False
def test_hide_llm_settings_true_when_env_var_true(self):
"""When HIDE_LLM_SETTINGS is 'true', hide_llm_settings flag is True."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_feature_flags,
)
with patch.dict(os.environ, {'HIDE_LLM_SETTINGS': 'true'}):
result = _get_feature_flags()
assert result.hide_llm_settings is True
def test_enable_jira_true_when_env_var_true(self):
"""When ENABLE_JIRA is 'true', enable_jira flag is True."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_feature_flags,
)
with patch.dict(os.environ, {'ENABLE_JIRA': 'true'}):
result = _get_feature_flags()
assert result.enable_jira is True
def test_enable_jira_dc_true_when_env_var_true(self):
"""When ENABLE_JIRA_DC is 'true', enable_jira_dc flag is True."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_feature_flags,
)
with patch.dict(os.environ, {'ENABLE_JIRA_DC': 'true'}):
result = _get_feature_flags()
assert result.enable_jira_dc is True
def test_enable_linear_true_when_env_var_true(self):
"""When ENABLE_LINEAR is 'true', enable_linear flag is True."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_feature_flags,
)
with patch.dict(os.environ, {'ENABLE_LINEAR': 'true'}):
result = _get_feature_flags()
assert result.enable_linear is True
def test_multiple_flags_can_be_set(self):
"""Multiple feature flags can be enabled simultaneously."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_feature_flags,
)
with patch.dict(
os.environ,
{
'ENABLE_BILLING': 'true',
'HIDE_LLM_SETTINGS': 'true',
'ENABLE_JIRA': 'false',
'ENABLE_LINEAR': 'true',
},
):
result = _get_feature_flags()
assert result.enable_billing is True
assert result.hide_llm_settings is True
assert result.enable_jira is False
assert result.enable_jira_dc is False
assert result.enable_linear is True
class TestGetMaintenanceStartTime:
"""Test cases for _get_maintenance_start_time helper function."""
def test_returns_datetime_when_valid_iso_timestamp_set(self):
"""When MAINTENANCE_START_TIME is a valid ISO 8601 timestamp, return parsed datetime."""
from datetime import datetime, timezone
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_maintenance_start_time,
)
with patch.dict(os.environ, {'MAINTENANCE_START_TIME': '2026-03-15T10:00:00Z'}):
result = _get_maintenance_start_time()
assert result == datetime(2026, 3, 15, 10, 0, 0, tzinfo=timezone.utc)
def test_returns_none_when_env_var_unset(self):
"""When MAINTENANCE_START_TIME is not set, return None."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_maintenance_start_time,
)
with patch.dict(os.environ, {}, clear=True):
os.environ.pop('MAINTENANCE_START_TIME', None)
result = _get_maintenance_start_time()
assert result is None
def test_returns_none_when_env_var_empty(self):
"""When MAINTENANCE_START_TIME is empty string, return None."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_maintenance_start_time,
)
with patch.dict(os.environ, {'MAINTENANCE_START_TIME': ''}):
result = _get_maintenance_start_time()
assert result is None
def test_returns_none_when_env_var_invalid(self):
"""When MAINTENANCE_START_TIME is invalid format, return None (graceful fallback)."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_maintenance_start_time,
)
with patch.dict(
os.environ, {'MAINTENANCE_START_TIME': 'not-a-valid-timestamp'}
):
result = _get_maintenance_start_time()
assert result is None
def test_strips_whitespace_from_env_var(self):
"""When MAINTENANCE_START_TIME has whitespace, strip it before parsing."""
from datetime import datetime, timezone
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_maintenance_start_time,
)
with patch.dict(
os.environ, {'MAINTENANCE_START_TIME': ' 2026-03-15T10:00:00Z '}
):
result = _get_maintenance_start_time()
assert result == datetime(2026, 3, 15, 10, 0, 0, tzinfo=timezone.utc)
class TestGetProvidersConfigured:
"""Test cases for _get_providers_configured helper function."""
def test_returns_empty_list_when_no_env_vars_set(self):
"""When no provider env vars are set, return empty list."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_providers_configured,
)
with patch.dict(os.environ, {}, clear=True):
# Remove any existing provider env vars
for var in [
'GITHUB_APP_CLIENT_ID',
'GITLAB_APP_CLIENT_ID',
'BITBUCKET_APP_CLIENT_ID',
'ENABLE_ENTERPRISE_SSO',
]:
os.environ.pop(var, None)
result = _get_providers_configured()
assert result == []
def test_includes_github_when_client_id_set(self):
"""When GITHUB_APP_CLIENT_ID is set, include GitHub in providers."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_providers_configured,
)
from openhands.integrations.service_types import ProviderType
with patch.dict(os.environ, {'GITHUB_APP_CLIENT_ID': 'some-client-id'}):
result = _get_providers_configured()
assert ProviderType.GITHUB in result
def test_includes_gitlab_when_client_id_set(self):
"""When GITLAB_APP_CLIENT_ID is set, include GitLab in providers."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_providers_configured,
)
from openhands.integrations.service_types import ProviderType
with patch.dict(os.environ, {'GITLAB_APP_CLIENT_ID': 'some-client-id'}):
result = _get_providers_configured()
assert ProviderType.GITLAB in result
def test_includes_bitbucket_when_client_id_set(self):
"""When BITBUCKET_APP_CLIENT_ID is set, include Bitbucket in providers."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_providers_configured,
)
from openhands.integrations.service_types import ProviderType
with patch.dict(os.environ, {'BITBUCKET_APP_CLIENT_ID': 'some-client-id'}):
result = _get_providers_configured()
assert ProviderType.BITBUCKET in result
def test_includes_enterprise_sso_when_enabled(self):
"""When ENABLE_ENTERPRISE_SSO is set, include Enterprise SSO in providers."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_providers_configured,
)
from openhands.integrations.service_types import ProviderType
with patch.dict(os.environ, {'ENABLE_ENTERPRISE_SSO': 'true'}):
result = _get_providers_configured()
assert ProviderType.ENTERPRISE_SSO in result
def test_excludes_provider_when_env_var_empty(self):
"""When env var is empty string, do not include provider."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_providers_configured,
)
from openhands.integrations.service_types import ProviderType
with patch.dict(os.environ, {'GITHUB_APP_CLIENT_ID': ''}):
result = _get_providers_configured()
assert ProviderType.GITHUB not in result
def test_excludes_provider_when_env_var_only_whitespace(self):
"""When env var is only whitespace, do not include provider."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_providers_configured,
)
from openhands.integrations.service_types import ProviderType
with patch.dict(os.environ, {'GITHUB_APP_CLIENT_ID': ' '}):
result = _get_providers_configured()
assert ProviderType.GITHUB not in result
def test_includes_multiple_providers(self):
"""Multiple providers can be configured simultaneously."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_providers_configured,
)
from openhands.integrations.service_types import ProviderType
with patch.dict(
os.environ,
{
'GITHUB_APP_CLIENT_ID': 'github-id',
'GITLAB_APP_CLIENT_ID': 'gitlab-id',
'BITBUCKET_APP_CLIENT_ID': '',
'ENABLE_ENTERPRISE_SSO': 'enabled',
},
):
result = _get_providers_configured()
assert ProviderType.GITHUB in result
assert ProviderType.GITLAB in result
assert ProviderType.BITBUCKET not in result
assert ProviderType.ENTERPRISE_SSO in result
assert len(result) == 3
class TestGetGithubAppSlug:
"""Test cases for _get_github_app_slug helper function."""
def test_returns_env_var_when_set(self):
"""When GITHUB_APP_SLUG is set, return that value."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_github_app_slug,
)
with patch.dict(os.environ, {'GITHUB_APP_SLUG': 'openhands-app'}):
result = _get_github_app_slug()
assert result == 'openhands-app'
def test_returns_none_when_env_var_unset(self):
"""When GITHUB_APP_SLUG is not set, return None."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_github_app_slug,
)
with patch.dict(os.environ, {}, clear=True):
os.environ.pop('GITHUB_APP_SLUG', None)
result = _get_github_app_slug()
assert result is None
def test_returns_none_when_env_var_empty(self):
"""When GITHUB_APP_SLUG is empty string, return None."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_github_app_slug,
)
with patch.dict(os.environ, {'GITHUB_APP_SLUG': ''}):
result = _get_github_app_slug()
assert result is None
def test_strips_whitespace_from_env_var(self):
"""When GITHUB_APP_SLUG has whitespace, strip it."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_github_app_slug,
)
with patch.dict(os.environ, {'GITHUB_APP_SLUG': ' openhands-app '}):
result = _get_github_app_slug()
assert result == 'openhands-app'
def test_returns_none_when_env_var_only_whitespace(self):
"""When GITHUB_APP_SLUG is only whitespace, return None."""
from openhands.app_server.web_client.default_web_client_config_injector import (
_get_github_app_slug,
)
with patch.dict(os.environ, {'GITHUB_APP_SLUG': ' '}):
result = _get_github_app_slug()
assert result is None