Files
mcp-nextcloud/tests/unit/test_config_validators.py
T
Chris CoutinhoandClaude Opus 4.6 c8e4cbe825 feat: implement dynaconf configuration management (ADR-024 phases 1-3)
Replace ~80 manual os.getenv() calls in config.py with dynaconf-backed
configuration, enabling TOML file-based config alongside existing env
var support. Zero breaking changes — Settings dataclass interface and
all consumers unchanged.

Phase 1: Create settings.toml with all config keys and defaults,
.secrets.toml.example template, update .gitignore, initialize Dynaconf
instance with envvar_prefix=False and environment section switching.

Phase 2: Wire adapter — replace os.getenv() with _dynaconf.get() in
get_settings(), get_document_processor_config(), and deprecation/
dependency resolution helpers. Automatic type coercion eliminates ~30
manual int()/float()/.lower()=="true" patterns.

Phase 3: Add 12 declarative validators for port ranges, positive
integers, enum constraints, and float ranges. Remove redundant negative
overlap check from Settings.__post_init__.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:22:19 +02:00

808 lines
29 KiB
Python

"""Unit tests for configuration validation and mode detection.
Tests cover:
- Mode detection logic
- Configuration validation for each mode
- Error message generation
- Edge cases and boundary conditions
"""
import os
from unittest.mock import patch
from nextcloud_mcp_server.config import Settings, _reload_config
from nextcloud_mcp_server.config_validators import (
AuthMode,
detect_auth_mode,
get_mode_summary,
validate_configuration,
)
class TestModeDetection:
"""Test auth mode detection from configuration."""
def test_multi_user_basic_mode_detection(self):
"""Test multi-user BasicAuth mode is detected."""
settings = Settings(
nextcloud_host="http://localhost",
enable_multi_user_basic_auth=True,
)
mode = detect_auth_mode(settings)
assert mode == AuthMode.MULTI_USER_BASIC
def test_single_user_basic_mode_detection(self):
"""Test single-user BasicAuth mode is detected."""
settings = Settings(
nextcloud_host="http://localhost",
nextcloud_username="admin",
nextcloud_password="password",
)
mode = detect_auth_mode(settings)
assert mode == AuthMode.SINGLE_USER_BASIC
def test_oauth_single_audience_default(self):
"""Test OAuth single-audience is default mode."""
settings = Settings(
nextcloud_host="http://localhost",
)
mode = detect_auth_mode(settings)
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
class TestSingleUserBasicValidation:
"""Test validation for single-user BasicAuth mode."""
def test_valid_minimal_config(self):
"""Test valid minimal single-user BasicAuth config."""
settings = Settings(
nextcloud_host="http://localhost",
nextcloud_username="admin",
nextcloud_password="password",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.SINGLE_USER_BASIC
assert len(errors) == 0
def test_valid_with_vector_sync(self):
"""Test valid config with vector sync enabled."""
settings = Settings(
nextcloud_host="http://localhost",
nextcloud_username="admin",
nextcloud_password="password",
vector_sync_enabled=True,
qdrant_location=":memory:",
ollama_base_url="http://ollama:11434",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.SINGLE_USER_BASIC
assert len(errors) == 0
def test_missing_required_host(self):
"""Test error when NEXTCLOUD_HOST is missing."""
settings = Settings(
nextcloud_username="admin",
nextcloud_password="password",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.SINGLE_USER_BASIC
assert any("nextcloud_host" in err.lower() for err in errors)
def test_missing_required_username(self):
"""Test that partial credentials fall back to OAuth mode."""
settings = Settings(
nextcloud_host="http://localhost",
nextcloud_password="password", # Password without username
)
mode, errors = validate_configuration(settings)
# Mode detection requires BOTH username AND password for single-user BasicAuth
# If only one is present, it defaults to OAuth single-audience
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
# In OAuth mode, having a password set is forbidden
assert any("nextcloud_password" in err.lower() for err in errors)
def test_missing_required_password(self):
"""Test that partial credentials fall back to OAuth mode."""
settings = Settings(
nextcloud_host="http://localhost",
nextcloud_username="admin", # Username without password
)
mode, errors = validate_configuration(settings)
# Mode detection requires BOTH username AND password for single-user BasicAuth
# If only one is present, it defaults to OAuth single-audience
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
# In OAuth mode, having a username set is forbidden
assert any("nextcloud_username" in err.lower() for err in errors)
def test_forbidden_multi_user_basic_auth(self):
"""Test error when ENABLE_MULTI_USER_BASIC_AUTH is set."""
settings = Settings(
nextcloud_host="http://localhost",
nextcloud_username="admin",
nextcloud_password="password",
enable_multi_user_basic_auth=True,
)
# Note: This will detect as MULTI_USER_BASIC due to priority
mode, errors = validate_configuration(settings)
assert mode == AuthMode.MULTI_USER_BASIC
# It will fail multi-user validation because username/password are forbidden
assert len(errors) > 0
def test_vector_sync_without_embedding_provider_uses_fallback(self):
"""Test that vector sync works with Simple provider fallback (no config needed)."""
settings = Settings(
nextcloud_host="http://localhost",
nextcloud_username="admin",
nextcloud_password="password",
vector_sync_enabled=True,
qdrant_location=":memory:",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.SINGLE_USER_BASIC
# Should pass - Simple provider is always available as fallback
assert len(errors) == 0
class TestMultiUserBasicValidation:
"""Test validation for multi-user BasicAuth mode."""
def test_valid_minimal_config(self):
"""Test valid minimal multi-user BasicAuth config."""
settings = Settings(
nextcloud_host="http://localhost",
enable_multi_user_basic_auth=True,
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.MULTI_USER_BASIC
assert len(errors) == 0
def test_valid_with_offline_access(self):
"""Test valid config with offline access enabled."""
settings = Settings(
nextcloud_host="http://localhost",
enable_multi_user_basic_auth=True,
enable_offline_access=True,
oidc_client_id="test-client",
oidc_client_secret="test-secret",
token_encryption_key="test-key-" + "a" * 32,
token_storage_db="/tmp/tokens.db",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.MULTI_USER_BASIC
assert len(errors) == 0
def test_missing_required_host(self):
"""Test error when NEXTCLOUD_HOST is missing."""
settings = Settings(
enable_multi_user_basic_auth=True,
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.MULTI_USER_BASIC
assert any("nextcloud_host" in err.lower() for err in errors)
def test_forbidden_username_password(self):
"""Test error when NEXTCLOUD_USERNAME/PASSWORD are set."""
settings = Settings(
nextcloud_host="http://localhost",
nextcloud_username="admin",
nextcloud_password="password",
enable_multi_user_basic_auth=True,
)
mode, errors = validate_configuration(settings)
# Multi-user BasicAuth has higher priority than single-user in detection
# (explicit flags come before credentials)
assert mode == AuthMode.MULTI_USER_BASIC
# Should report errors for forbidden username/password
assert any("nextcloud_username" in err.lower() for err in errors)
assert any("nextcloud_password" in err.lower() for err in errors)
def test_offline_access_missing_oauth_credentials(self):
"""Test that offline access works without OAuth credentials (will use DCR)."""
settings = Settings(
nextcloud_host="http://localhost",
enable_multi_user_basic_auth=True,
enable_offline_access=True,
token_encryption_key="test-key-" + "a" * 32,
token_storage_db="/tmp/tokens.db",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.MULTI_USER_BASIC
# No errors - DCR will be used as fallback (consistent with OAuth modes)
assert len(errors) == 0
def test_offline_access_missing_encryption_key(self):
"""Test error when offline access enabled but encryption key missing."""
settings = Settings(
nextcloud_host="http://localhost",
enable_multi_user_basic_auth=True,
enable_offline_access=True,
oidc_client_id="test-client",
oidc_client_secret="test-secret",
token_storage_db="/tmp/tokens.db",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.MULTI_USER_BASIC
assert any("token_encryption_key" in err.lower() for err in errors)
def test_vector_sync_auto_enables_background_ops_in_multi_user_mode(self):
"""Test vector sync automatically enables background operations in multi-user mode (ADR-021)."""
# Before ADR-021: This would have failed validation (required explicit ENABLE_OFFLINE_ACCESS)
# After ADR-021: vector_sync_enabled auto-enables background operations
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"ENABLE_MULTI_USER_BASIC_AUTH": "true",
"VECTOR_SYNC_ENABLED": "true", # Using old name for backward compat test
"QDRANT_LOCATION": ":memory:",
"OLLAMA_BASE_URL": "http://ollama:11434",
"TOKEN_ENCRYPTION_KEY": "test-key",
"TOKEN_STORAGE_DB": "/tmp/test.db",
"NEXTCLOUD_OIDC_CLIENT_ID": "test-client-id",
"NEXTCLOUD_OIDC_CLIENT_SECRET": "test-client-secret",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
mode, errors = validate_configuration(settings)
assert mode == AuthMode.MULTI_USER_BASIC
# Should have no errors - background operations auto-enabled
assert len(errors) == 0
# Verify background operations were auto-enabled
assert settings.enable_offline_access is True
class TestOAuthSingleAudienceValidation:
"""Test validation for OAuth single-audience mode."""
def test_valid_minimal_config(self):
"""Test valid minimal OAuth single-audience config."""
settings = Settings(
nextcloud_host="http://localhost",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
assert len(errors) == 0
def test_valid_with_static_credentials(self):
"""Test valid config with static OAuth credentials."""
settings = Settings(
nextcloud_host="http://localhost",
oidc_client_id="test-client",
oidc_client_secret="test-secret",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
assert len(errors) == 0
def test_valid_with_offline_access(self):
"""Test valid config with offline access."""
settings = Settings(
nextcloud_host="http://localhost",
oidc_client_id="test-client",
oidc_client_secret="test-secret",
enable_offline_access=True,
token_encryption_key="test-key-" + "a" * 32,
token_storage_db="/tmp/tokens.db",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
assert len(errors) == 0
def test_forbidden_username_password(self):
"""Test that username/password trigger single-user mode instead."""
settings = Settings(
nextcloud_host="http://localhost",
nextcloud_username="admin",
nextcloud_password="password",
)
mode, errors = validate_configuration(settings)
# This should detect as SINGLE_USER_BASIC
assert mode == AuthMode.SINGLE_USER_BASIC
def test_offline_access_missing_encryption_key(self):
"""Test error when offline access enabled but encryption key missing."""
settings = Settings(
nextcloud_host="http://localhost",
enable_offline_access=True,
token_storage_db="/tmp/tokens.db",
)
mode, errors = validate_configuration(settings)
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
assert any("token_encryption_key" in err.lower() for err in errors)
def test_vector_sync_auto_enables_background_ops_in_oauth_mode(self):
"""Test vector sync automatically enables background operations in OAuth mode (ADR-021)."""
# Before ADR-021: This would have failed validation (required explicit ENABLE_OFFLINE_ACCESS)
# After ADR-021: vector_sync_enabled auto-enables background operations in multi-user modes
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"VECTOR_SYNC_ENABLED": "true",
"QDRANT_LOCATION": ":memory:",
"OLLAMA_BASE_URL": "http://ollama:11434",
"TOKEN_ENCRYPTION_KEY": "test-key",
"TOKEN_STORAGE_DB": "/tmp/test.db",
# Note: No username/password = OAuth mode
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
mode, errors = validate_configuration(settings)
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
# Should have no errors - background operations auto-enabled
assert len(errors) == 0
# Verify background operations were auto-enabled
assert settings.enable_offline_access is True
class TestModeSummary:
"""Test mode summary generation."""
def test_single_user_basic_summary(self):
"""Test summary for single-user BasicAuth mode."""
summary = get_mode_summary(AuthMode.SINGLE_USER_BASIC)
assert "single_user_basic" in summary
assert "NEXTCLOUD_HOST" in summary
assert "NEXTCLOUD_USERNAME" in summary
assert "NEXTCLOUD_PASSWORD" in summary
assert "VECTOR_SYNC_ENABLED" in summary
class TestEdgeCases:
"""Test edge cases and boundary conditions."""
def test_empty_string_treated_as_missing(self):
"""Test that empty strings are treated as missing values."""
settings = Settings(
nextcloud_host="", # Empty string
nextcloud_username="admin",
nextcloud_password="password",
)
mode, errors = validate_configuration(settings)
# Should fail because nextcloud_host is effectively missing
assert any("nextcloud_host" in err.lower() for err in errors)
def test_whitespace_treated_as_missing(self):
"""Test that whitespace-only strings are treated as missing."""
settings = Settings(
nextcloud_host=" ", # Whitespace only
nextcloud_username="admin",
nextcloud_password="password",
)
mode, errors = validate_configuration(settings)
# Should fail because nextcloud_host is effectively missing
assert any("nextcloud_host" in err.lower() for err in errors)
def test_multiple_errors_reported(self):
"""Test that multiple errors are all reported."""
settings = Settings(
# Missing all required fields for single-user BasicAuth
)
mode, errors = validate_configuration(settings)
# Should have errors for missing host (OAuth mode is default)
assert len(errors) > 0
class TestConfigurationConsolidation:
"""Test ADR-021 configuration consolidation and backward compatibility.
Tests verify:
- New variable names work (ENABLE_SEMANTIC_SEARCH, ENABLE_BACKGROUND_OPERATIONS)
- Old variable names still work (VECTOR_SYNC_ENABLED, ENABLE_OFFLINE_ACCESS)
- Deprecation warnings are logged
- Auto-enablement of background operations in multi-user modes
"""
def test_new_semantic_search_variable_name(self):
"""Test ENABLE_SEMANTIC_SEARCH (new name) works correctly."""
with patch.dict(
os.environ,
{
"ENABLE_SEMANTIC_SEARCH": "true",
"QDRANT_LOCATION": ":memory:",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
assert settings.vector_sync_enabled is True
def test_old_vector_sync_variable_name_backward_compat(self):
"""Test VECTOR_SYNC_ENABLED (old name) still works for backward compatibility."""
with patch.dict(
os.environ,
{
"VECTOR_SYNC_ENABLED": "true",
"QDRANT_LOCATION": ":memory:",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
assert settings.vector_sync_enabled is True
def test_new_background_operations_variable_name(self):
"""Test ENABLE_BACKGROUND_OPERATIONS (new name) works correctly."""
with patch.dict(
os.environ,
{
"ENABLE_BACKGROUND_OPERATIONS": "true",
"TOKEN_ENCRYPTION_KEY": "test-key",
"TOKEN_STORAGE_DB": "/tmp/test.db",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
assert settings.enable_offline_access is True
def test_old_offline_access_variable_name_backward_compat(self):
"""Test ENABLE_OFFLINE_ACCESS (old name) still works for backward compatibility."""
with patch.dict(
os.environ,
{
"ENABLE_OFFLINE_ACCESS": "true",
"TOKEN_ENCRYPTION_KEY": "test-key",
"TOKEN_STORAGE_DB": "/tmp/test.db",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
assert settings.enable_offline_access is True
def test_semantic_search_auto_enables_background_ops_in_oauth_mode(self):
"""Test ENABLE_SEMANTIC_SEARCH automatically enables background operations in OAuth mode."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"ENABLE_SEMANTIC_SEARCH": "true",
"QDRANT_LOCATION": ":memory:",
"TOKEN_ENCRYPTION_KEY": "test-key",
"TOKEN_STORAGE_DB": "/tmp/test.db",
# Note: No NEXTCLOUD_USERNAME/PASSWORD = OAuth mode
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
# Semantic search enabled
assert settings.vector_sync_enabled is True
# Background operations auto-enabled (even though not explicitly set)
assert settings.enable_offline_access is True
def test_semantic_search_does_not_auto_enable_in_single_user_mode(self):
"""Test ENABLE_SEMANTIC_SEARCH does NOT auto-enable background ops in single-user mode."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"NEXTCLOUD_USERNAME": "admin",
"NEXTCLOUD_PASSWORD": "password",
"ENABLE_SEMANTIC_SEARCH": "true",
"QDRANT_LOCATION": ":memory:",
# Note: Username/password set = single-user BasicAuth mode
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
# Semantic search enabled
assert settings.vector_sync_enabled is True
# Background operations NOT auto-enabled (not needed in single-user mode)
assert settings.enable_offline_access is False
def test_explicit_background_ops_still_works(self):
"""Test explicitly setting ENABLE_BACKGROUND_OPERATIONS works even without semantic search."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"ENABLE_BACKGROUND_OPERATIONS": "true",
"TOKEN_ENCRYPTION_KEY": "test-key",
"TOKEN_STORAGE_DB": "/tmp/test.db",
# Note: No semantic search enabled
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
# Semantic search NOT enabled
assert settings.vector_sync_enabled is False
# Background operations explicitly enabled
assert settings.enable_offline_access is True
def test_both_old_and_new_semantic_search_names_prefers_new(self):
"""Test setting both ENABLE_SEMANTIC_SEARCH and VECTOR_SYNC_ENABLED uses new name."""
with patch.dict(
os.environ,
{
"ENABLE_SEMANTIC_SEARCH": "true",
"VECTOR_SYNC_ENABLED": "false", # Old name says false
"QDRANT_LOCATION": ":memory:",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
# Should use new name value (true)
assert settings.vector_sync_enabled is True
def test_both_old_and_new_background_ops_names_prefers_new(self):
"""Test setting both ENABLE_BACKGROUND_OPERATIONS and ENABLE_OFFLINE_ACCESS uses new name."""
with patch.dict(
os.environ,
{
"ENABLE_BACKGROUND_OPERATIONS": "true",
"ENABLE_OFFLINE_ACCESS": "false", # Old name says false
"TOKEN_ENCRYPTION_KEY": "test-key",
"TOKEN_STORAGE_DB": "/tmp/test.db",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
# Should use new name value (true)
assert settings.enable_offline_access is True
def test_validation_no_longer_requires_both_variables(self):
"""Test validation no longer requires explicit ENABLE_OFFLINE_ACCESS when semantic search enabled."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"ENABLE_MULTI_USER_BASIC_AUTH": "true",
"ENABLE_SEMANTIC_SEARCH": "true",
"QDRANT_LOCATION": ":memory:",
"TOKEN_ENCRYPTION_KEY": "test-key",
"TOKEN_STORAGE_DB": "/tmp/test.db",
# OAuth credentials required for app password retrieval (when background ops enabled)
"NEXTCLOUD_OIDC_CLIENT_ID": "test-client-id",
"NEXTCLOUD_OIDC_CLIENT_SECRET": "test-client-secret",
# Note: ENABLE_OFFLINE_ACCESS not set - should auto-enable
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
mode, errors = validate_configuration(settings)
# Should have no validation errors
# (Previously would have required explicit ENABLE_OFFLINE_ACCESS)
assert len(errors) == 0
assert mode == AuthMode.MULTI_USER_BASIC
# Verify background operations were auto-enabled
assert settings.enable_offline_access is True
class TestExplicitModeSelection:
"""Test ADR-021 explicit mode selection via MCP_DEPLOYMENT_MODE.
Tests verify:
- Explicit mode selection works for all modes
- Invalid mode names raise ValueError
- Explicit mode takes precedence over auto-detection
"""
def test_explicit_single_user_basic_mode(self):
"""Test explicit single_user_basic mode selection."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"MCP_DEPLOYMENT_MODE": "single_user_basic",
"NEXTCLOUD_USERNAME": "admin",
"NEXTCLOUD_PASSWORD": "password",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
mode = detect_auth_mode(settings)
assert mode == AuthMode.SINGLE_USER_BASIC
def test_explicit_multi_user_basic_mode(self):
"""Test explicit multi_user_basic mode selection."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"MCP_DEPLOYMENT_MODE": "multi_user_basic",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
mode = detect_auth_mode(settings)
assert mode == AuthMode.MULTI_USER_BASIC
def test_explicit_oauth_single_audience_mode(self):
"""Test explicit oauth_single_audience mode selection."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"MCP_DEPLOYMENT_MODE": "oauth_single_audience",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
mode = detect_auth_mode(settings)
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
def test_invalid_deployment_mode_raises_error(self):
"""Test invalid MCP_DEPLOYMENT_MODE raises ValueError."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"MCP_DEPLOYMENT_MODE": "invalid_mode",
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
# Should raise ValueError with clear message
try:
detect_auth_mode(settings)
assert False, "Should have raised ValueError"
except ValueError as e:
assert "Invalid MCP_DEPLOYMENT_MODE" in str(e)
assert "invalid_mode" in str(e)
assert "Valid values:" in str(e)
def test_explicit_mode_overrides_auto_detection(self):
"""Test explicit mode takes precedence over auto-detection."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"NEXTCLOUD_USERNAME": "admin", # Would auto-detect as single_user_basic
"NEXTCLOUD_PASSWORD": "password",
"MCP_DEPLOYMENT_MODE": "oauth_single_audience", # Explicit override
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
mode = detect_auth_mode(settings)
# Should use explicit mode, not auto-detected mode
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
def test_case_insensitive_mode_names(self):
"""Test MCP_DEPLOYMENT_MODE is case-insensitive."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"MCP_DEPLOYMENT_MODE": "OAUTH_SINGLE_AUDIENCE", # Uppercase
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
mode = detect_auth_mode(settings)
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE
def test_whitespace_in_mode_name_stripped(self):
"""Test whitespace in MCP_DEPLOYMENT_MODE is stripped."""
with patch.dict(
os.environ,
{
"NEXTCLOUD_HOST": "http://localhost:8080",
"MCP_DEPLOYMENT_MODE": " oauth_single_audience ", # Whitespace
},
clear=True,
):
from nextcloud_mcp_server.config import get_settings
_reload_config()
settings = get_settings()
mode = detect_auth_mode(settings)
assert mode == AuthMode.OAUTH_SINGLE_AUDIENCE