diff --git a/app/config.py b/app/config.py index 7ffd15e1..bf9c23c4 100644 --- a/app/config.py +++ b/app/config.py @@ -49,18 +49,30 @@ class Settings(BaseSettings): debug: bool = False # Default to False # Making Dropbox optional + dropbox_enabled: bool = Field( + default=True, + description="Enable Dropbox as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) dropbox_app_key: Optional[str] = None dropbox_app_secret: Optional[str] = None dropbox_folder: Optional[str] = None dropbox_refresh_token: Optional[str] = None # Making Nextcloud optional + nextcloud_enabled: bool = Field( + default=True, + description="Enable Nextcloud as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) nextcloud_upload_url: Optional[str] = None nextcloud_username: Optional[str] = None nextcloud_password: Optional[str] = None nextcloud_folder: Optional[str] = None # Making Paperless optional + paperless_enabled: bool = Field( + default=True, + description="Enable Paperless-ngx as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) paperless_ngx_api_token: Optional[str] = None paperless_host: Optional[str] = None paperless_custom_field_absender: Optional[str] = None # Name of the "absender" custom field in Paperless @@ -394,6 +406,10 @@ class Settings(BaseSettings): imap2_delete_after_process: bool = False # Google Drive settings + google_drive_enabled: bool = Field( + default=True, + description="Enable Google Drive as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) google_drive_credentials_json: Optional[str] = "" google_drive_folder_id: Optional[str] = "" google_drive_delegate_to: Optional[str] = "" # Optional delegated user email @@ -405,6 +421,10 @@ class Settings(BaseSettings): google_drive_refresh_token: Optional[str] = "" # WebDAV settings + webdav_enabled: bool = Field( + default=True, + description="Enable WebDAV as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) webdav_url: Optional[str] = None webdav_username: Optional[str] = None webdav_password: Optional[str] = None @@ -412,6 +432,10 @@ class Settings(BaseSettings): webdav_verify_ssl: bool = True # FTP settings + ftp_enabled: bool = Field( + default=True, + description="Enable FTP as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) ftp_host: Optional[str] = None ftp_port: Optional[int] = 21 ftp_username: Optional[str] = None @@ -421,6 +445,10 @@ class Settings(BaseSettings): ftp_allow_plaintext: bool = True # Default to allowing plaintext fallback # SFTP settings + sftp_enabled: bool = Field( + default=True, + description="Enable SFTP as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) sftp_host: Optional[str] = None sftp_port: Optional[int] = 22 sftp_username: Optional[str] = None @@ -442,6 +470,10 @@ class Settings(BaseSettings): email_default_recipient: Optional[str] = None # Email destination settings (dedicated SMTP for document delivery – decoupled from shared email above) + dest_email_enabled: bool = Field( + default=True, + description="Enable Email as an upload destination. Set to False to disable document delivery via email even when credentials are configured.", + ) dest_email_host: Optional[str] = None dest_email_port: Optional[int] = 587 dest_email_username: Optional[str] = None @@ -451,6 +483,10 @@ class Settings(BaseSettings): dest_email_default_recipient: Optional[str] = None # Fallback recipient for document delivery # OneDrive settings + onedrive_enabled: bool = Field( + default=True, + description="Enable OneDrive as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) onedrive_client_id: Optional[str] = None onedrive_client_secret: Optional[str] = None onedrive_tenant_id: Optional[str] = "common" # Default to "common" for personal accounts @@ -458,6 +494,10 @@ class Settings(BaseSettings): onedrive_folder_path: Optional[str] = None # AWS S3 settings + s3_enabled: bool = Field( + default=True, + description="Enable Amazon S3 as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) aws_access_key_id: Optional[str] = None aws_secret_access_key: Optional[str] = None aws_region: Optional[str] = "us-east-1" # Default region @@ -467,6 +507,10 @@ class Settings(BaseSettings): s3_acl: Optional[str] = "private" # Default ACL # iCloud Drive settings + icloud_enabled: bool = Field( + default=True, + description="Enable iCloud Drive as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) icloud_username: Optional[str] = None # Apple ID email address icloud_password: Optional[str] = None # App-specific password (required for 2FA accounts) icloud_folder: Optional[str] = None # Target folder path in iCloud Drive (e.g. "Documents/Uploads") diff --git a/app/tasks/send_to_all.py b/app/tasks/send_to_all.py index e4ddd5b6..214e0225 100644 --- a/app/tasks/send_to_all.py +++ b/app/tasks/send_to_all.py @@ -26,18 +26,32 @@ logger = logging.getLogger(__name__) def _should_upload_to_dropbox(): - return bool(settings.dropbox_app_key and settings.dropbox_app_secret and settings.dropbox_refresh_token) + return bool( + getattr(settings, "dropbox_enabled", True) + and settings.dropbox_app_key + and settings.dropbox_app_secret + and settings.dropbox_refresh_token + ) def _should_upload_to_nextcloud(): - return bool(settings.nextcloud_upload_url and settings.nextcloud_username and settings.nextcloud_password) + return bool( + getattr(settings, "nextcloud_enabled", True) + and settings.nextcloud_upload_url + and settings.nextcloud_username + and settings.nextcloud_password + ) def _should_upload_to_paperless(): - return bool(settings.paperless_ngx_api_token and settings.paperless_host) + return bool( + getattr(settings, "paperless_enabled", True) and settings.paperless_ngx_api_token and settings.paperless_host + ) def _should_upload_to_google_drive(): + if not getattr(settings, "google_drive_enabled", True): + return False # Check for OAuth configuration if getattr(settings, "google_drive_use_oauth", False): return bool( @@ -52,20 +66,33 @@ def _should_upload_to_google_drive(): def _should_upload_to_webdav(): - return bool(settings.webdav_url and settings.webdav_username and settings.webdav_password) + return bool( + getattr(settings, "webdav_enabled", True) + and settings.webdav_url + and settings.webdav_username + and settings.webdav_password + ) def _should_upload_to_ftp(): - return bool(settings.ftp_host and settings.ftp_username and settings.ftp_password) + return bool( + getattr(settings, "ftp_enabled", True) and settings.ftp_host and settings.ftp_username and settings.ftp_password + ) def _should_upload_to_sftp(): - return bool(settings.sftp_host and settings.sftp_username and (settings.sftp_password or settings.sftp_private_key)) + return bool( + getattr(settings, "sftp_enabled", True) + and settings.sftp_host + and settings.sftp_username + and (settings.sftp_password or settings.sftp_private_key) + ) def _should_upload_to_email(): return bool( - settings.dest_email_host + getattr(settings, "dest_email_enabled", True) + and settings.dest_email_host and settings.dest_email_username and settings.dest_email_password and settings.dest_email_default_recipient @@ -73,22 +100,32 @@ def _should_upload_to_email(): def _should_upload_to_onedrive(): - return bool(settings.onedrive_client_id and settings.onedrive_client_secret and settings.onedrive_refresh_token) + return bool( + getattr(settings, "onedrive_enabled", True) + and settings.onedrive_client_id + and settings.onedrive_client_secret + and settings.onedrive_refresh_token + ) def _should_upload_to_s3(): - return bool(settings.s3_bucket_name and settings.aws_access_key_id and settings.aws_secret_access_key) + return bool( + getattr(settings, "s3_enabled", True) + and settings.s3_bucket_name + and settings.aws_access_key_id + and settings.aws_secret_access_key + ) def _should_upload_to_icloud(): - return bool(settings.icloud_username and settings.icloud_password) + return bool(getattr(settings, "icloud_enabled", True) and settings.icloud_username and settings.icloud_password) def get_configured_services_from_validator(): """ - Use the config validator to determine which services are configured properly. + Use the config validator to determine which services are configured and enabled. Returns a dictionary with service names as keys and boolean values indicating - whether they're properly configured. + whether they're properly configured AND explicitly enabled. """ providers = get_provider_status() @@ -109,7 +146,8 @@ def get_configured_services_from_validator(): result = {} for provider_name, internal_name in service_map.items(): if provider_name in providers: - result[internal_name] = providers[provider_name].get("configured", False) + provider = providers[provider_name] + result[internal_name] = provider.get("configured", False) and provider.get("enabled", True) return result diff --git a/app/utils/config_validator/providers.py b/app/utils/config_validator/providers.py index 9fce74b6..9a1e7069 100644 --- a/app/utils/config_validator/providers.py +++ b/app/utils/config_validator/providers.py @@ -144,7 +144,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "dropbox_app_secret", None) and getattr(settings, "dropbox_refresh_token", None) ), - "enabled": True, + "enabled": getattr(settings, "dropbox_enabled", True), "description": "Upload files to Dropbox cloud storage", "details": { "folder": getattr(settings, "dropbox_folder", "Not set"), @@ -161,7 +161,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: "configured": bool( getattr(settings, "dest_email_host", None) and getattr(settings, "dest_email_default_recipient", None) ), - "enabled": True, + "enabled": getattr(settings, "dest_email_enabled", True), "description": "Send documents via email", "details": { "host": getattr(settings, "dest_email_host", "Not set"), @@ -183,7 +183,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "ftp_username", None) and getattr(settings, "ftp_password", None) ), - "enabled": True, + "enabled": getattr(settings, "ftp_enabled", True), "description": "Upload files to FTP server", "details": { "host": getattr(settings, "ftp_host", "Not set"), @@ -214,7 +214,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: "name": "Google Drive", "icon": "fa-brands fa-google-drive", "configured": is_configured and bool(getattr(settings, "google_drive_folder_id", None)), - "enabled": True, + "enabled": getattr(settings, "google_drive_enabled", True), "description": "Store documents in Google Drive", "details": { "auth_type": "OAuth" if use_oauth else "Service Account", @@ -250,7 +250,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "nextcloud_username", None) and getattr(settings, "nextcloud_password", None) ), - "enabled": True, + "enabled": getattr(settings, "nextcloud_enabled", True), "description": "Store documents in NextCloud", "details": { "url": getattr(settings, "nextcloud_upload_url", "Not set"), @@ -270,7 +270,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "onedrive_client_secret", None) and getattr(settings, "onedrive_refresh_token", None) ), - "enabled": True, + "enabled": getattr(settings, "onedrive_enabled", True), "description": "Store documents in Microsoft OneDrive", "details": { "client_id": getattr(settings, "onedrive_client_id", "Not set"), @@ -288,7 +288,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: "configured": bool( getattr(settings, "paperless_host", None) and getattr(settings, "paperless_ngx_api_token", None) ), - "enabled": True, + "enabled": getattr(settings, "paperless_enabled", True), "description": "Document management system for digital archives", "details": { "host": getattr(settings, "paperless_host", "Not set"), @@ -305,7 +305,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "aws_access_key_id", None) and getattr(settings, "aws_secret_access_key", None) ), - "enabled": True, + "enabled": getattr(settings, "s3_enabled", True), "description": "Store documents in S3-compatible object storage", "details": { "bucket": getattr(settings, "s3_bucket_name", "Not set"), @@ -327,7 +327,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "sftp_username", None) and (getattr(settings, "sftp_password", None) or getattr(settings, "sftp_private_key", None)) ), - "enabled": True, + "enabled": getattr(settings, "sftp_enabled", True), "description": "Upload files to SFTP server", "details": { "host": getattr(settings, "sftp_host", "Not set"), @@ -362,7 +362,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "webdav_username", None) and getattr(settings, "webdav_password", None) ), - "enabled": True, + "enabled": getattr(settings, "webdav_enabled", True), "description": "Store documents on WebDAV servers", "details": { "url": getattr(settings, "webdav_url", "Not set"), @@ -378,7 +378,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: "name": "iCloud Drive", "icon": "fa-brands fa-apple", "configured": bool(getattr(settings, "icloud_username", None) and getattr(settings, "icloud_password", None)), - "enabled": True, + "enabled": getattr(settings, "icloud_enabled", True), "description": "Store documents in Apple iCloud Drive", "details": { "username": getattr(settings, "icloud_username", "Not set"), diff --git a/app/utils/settings_service.py b/app/utils/settings_service.py index 78188b9e..5b66b306 100644 --- a/app/utils/settings_service.py +++ b/app/utils/settings_service.py @@ -495,6 +495,14 @@ SETTING_METADATA = { "options": ["us", "eu"], }, # Storage Providers - Dropbox + "dropbox_enabled": { + "category": "Storage Providers", + "description": "Enable Dropbox as an upload destination. When disabled, no documents will be sent to Dropbox even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "dropbox_app_key": { "category": "Storage Providers", "description": "Dropbox app key for OAuth authentication", @@ -528,6 +536,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - Nextcloud + "nextcloud_enabled": { + "category": "Storage Providers", + "description": "Enable Nextcloud as an upload destination. When disabled, no documents will be sent to Nextcloud even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "nextcloud_upload_url": { "category": "Storage Providers", "description": "Nextcloud WebDAV upload URL", @@ -561,6 +577,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - Paperless-ngx + "paperless_enabled": { + "category": "Storage Providers", + "description": "Enable Paperless-ngx as an upload destination. When disabled, no documents will be sent to Paperless-ngx even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "paperless_ngx_api_token": { "category": "Storage Providers", "description": "Paperless-ngx API authentication token", @@ -578,6 +602,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - Google Drive + "google_drive_enabled": { + "category": "Storage Providers", + "description": "Enable Google Drive as an upload destination. When disabled, no documents will be sent to Google Drive even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "google_drive_credentials_json": { "category": "Storage Providers", "description": "Google Drive service account credentials JSON", @@ -635,6 +667,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - OneDrive + "onedrive_enabled": { + "category": "Storage Providers", + "description": "Enable OneDrive as an upload destination. When disabled, no documents will be sent to OneDrive even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "onedrive_client_id": { "category": "Storage Providers", "description": "OneDrive OAuth client ID", @@ -676,6 +716,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - WebDAV + "webdav_enabled": { + "category": "Storage Providers", + "description": "Enable WebDAV as an upload destination. When disabled, no documents will be sent to WebDAV even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "webdav_url": { "category": "Storage Providers", "description": "WebDAV server URL", @@ -717,6 +765,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - FTP + "ftp_enabled": { + "category": "Storage Providers", + "description": "Enable FTP as an upload destination. When disabled, no documents will be sent to FTP even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "ftp_host": { "category": "Storage Providers", "description": "FTP server hostname or IP address", @@ -774,6 +830,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - SFTP + "sftp_enabled": { + "category": "Storage Providers", + "description": "Enable SFTP as an upload destination. When disabled, no documents will be sent to SFTP even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "sftp_host": { "category": "Storage Providers", "description": "SFTP server hostname or IP address", @@ -839,6 +903,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - iCloud Drive + "icloud_enabled": { + "category": "Storage Providers", + "description": "Enable iCloud Drive as an upload destination. When disabled, no documents will be sent to iCloud Drive even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "icloud_username": { "category": "Storage Providers", "description": "Apple ID email address for iCloud Drive authentication", @@ -872,6 +944,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - AWS S3 + "s3_enabled": { + "category": "Storage Providers", + "description": "Enable Amazon S3 as an upload destination. When disabled, no documents will be sent to S3 even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "aws_access_key_id": { "category": "Storage Providers", "description": "AWS access key ID for S3", @@ -1005,6 +1085,14 @@ SETTING_METADATA = { "restart_required": False, }, # Email Destination Settings (dedicated SMTP for document delivery) + "dest_email_enabled": { + "category": "Email Destination", + "description": "Enable Email as an upload destination. When disabled, no documents will be delivered via email even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "dest_email_host": { "category": "Email Destination", "description": "SMTP server hostname for document delivery (separate from shared email settings)", diff --git a/tests/test_send_to_all.py b/tests/test_send_to_all.py index 2faad8fa..0bab975f 100644 --- a/tests/test_send_to_all.py +++ b/tests/test_send_to_all.py @@ -163,6 +163,121 @@ class TestShouldUploadFunctions: assert _should_upload_to_icloud() is False +@pytest.mark.unit +class TestShouldUploadEnabledFlag: + """Test that the _should_upload_to_* functions respect the explicit enabled flag.""" + + @patch("app.tasks.send_to_all.settings") + def test_dropbox_disabled_with_credentials(self, mock_settings): + """Test Dropbox upload is blocked when disabled even with valid credentials.""" + mock_settings.dropbox_enabled = False + mock_settings.dropbox_app_key = "key" + mock_settings.dropbox_app_secret = "secret" + mock_settings.dropbox_refresh_token = "token" + + assert _should_upload_to_dropbox() is False + + @patch("app.tasks.send_to_all.settings") + def test_nextcloud_disabled_with_credentials(self, mock_settings): + """Test Nextcloud upload is blocked when disabled even with valid credentials.""" + mock_settings.nextcloud_enabled = False + mock_settings.nextcloud_upload_url = "https://nextcloud.example.com" + mock_settings.nextcloud_username = "user" + mock_settings.nextcloud_password = "pass" + + assert _should_upload_to_nextcloud() is False + + @patch("app.tasks.send_to_all.settings") + def test_paperless_disabled_with_credentials(self, mock_settings): + """Test Paperless upload is blocked when disabled even with valid credentials.""" + mock_settings.paperless_enabled = False + mock_settings.paperless_ngx_api_token = "token" + mock_settings.paperless_host = "https://paperless.example.com" + + assert _should_upload_to_paperless() is False + + @patch("app.tasks.send_to_all.settings") + def test_google_drive_disabled_with_credentials(self, mock_settings): + """Test Google Drive upload is blocked when disabled even with valid credentials.""" + mock_settings.google_drive_enabled = False + mock_settings.google_drive_use_oauth = False + mock_settings.google_drive_credentials_json = '{"type": "service_account"}' + mock_settings.google_drive_folder_id = "folder_id" + + assert _should_upload_to_google_drive() is False + + @patch("app.tasks.send_to_all.settings") + def test_webdav_disabled_with_credentials(self, mock_settings): + """Test WebDAV upload is blocked when disabled even with valid credentials.""" + mock_settings.webdav_enabled = False + mock_settings.webdav_url = "https://webdav.example.com" + mock_settings.webdav_username = "user" + mock_settings.webdav_password = "pass" + + assert _should_upload_to_webdav() is False + + @patch("app.tasks.send_to_all.settings") + def test_ftp_disabled_with_credentials(self, mock_settings): + """Test FTP upload is blocked when disabled even with valid credentials.""" + mock_settings.ftp_enabled = False + mock_settings.ftp_host = "ftp.example.com" + mock_settings.ftp_username = "user" + mock_settings.ftp_password = "pass" + + assert _should_upload_to_ftp() is False + + @patch("app.tasks.send_to_all.settings") + def test_sftp_disabled_with_credentials(self, mock_settings): + """Test SFTP upload is blocked when disabled even with valid credentials.""" + mock_settings.sftp_enabled = False + mock_settings.sftp_host = "sftp.example.com" + mock_settings.sftp_username = "user" + mock_settings.sftp_password = "pass" + mock_settings.sftp_private_key = None + + assert _should_upload_to_sftp() is False + + @patch("app.tasks.send_to_all.settings") + def test_email_disabled_with_credentials(self, mock_settings): + """Test email upload is blocked when disabled even with valid credentials.""" + mock_settings.dest_email_enabled = False + mock_settings.dest_email_host = "smtp.example.com" + mock_settings.dest_email_username = "user" + mock_settings.dest_email_password = "pass" + mock_settings.dest_email_default_recipient = "recipient@example.com" + + assert _should_upload_to_email() is False + + @patch("app.tasks.send_to_all.settings") + def test_onedrive_disabled_with_credentials(self, mock_settings): + """Test OneDrive upload is blocked when disabled even with valid credentials.""" + mock_settings.onedrive_enabled = False + mock_settings.onedrive_client_id = "client_id" + mock_settings.onedrive_client_secret = "client_secret" + mock_settings.onedrive_refresh_token = "refresh_token" + + assert _should_upload_to_onedrive() is False + + @patch("app.tasks.send_to_all.settings") + def test_s3_disabled_with_credentials(self, mock_settings): + """Test S3 upload is blocked when disabled even with valid credentials.""" + mock_settings.s3_enabled = False + mock_settings.s3_bucket_name = "my-bucket" + mock_settings.aws_access_key_id = "key_id" + mock_settings.aws_secret_access_key = "secret_key" + + assert _should_upload_to_s3() is False + + @patch("app.tasks.send_to_all.settings") + def test_icloud_disabled_with_credentials(self, mock_settings): + """Test iCloud upload is blocked when disabled even with valid credentials.""" + mock_settings.icloud_enabled = False + mock_settings.icloud_username = "user@example.com" + mock_settings.icloud_password = "app-specific-password" + + assert _should_upload_to_icloud() is False + + @pytest.mark.unit class TestGetConfiguredServicesFromValidator: """Test get_configured_services_from_validator function.""" @@ -171,9 +286,9 @@ class TestGetConfiguredServicesFromValidator: def test_returns_configured_services(self, mock_get_status): """Test that configured services are returned correctly.""" mock_get_status.return_value = { - "Dropbox": {"configured": True}, - "NextCloud": {"configured": False}, - "S3 Storage": {"configured": True}, + "Dropbox": {"configured": True, "enabled": True}, + "NextCloud": {"configured": False, "enabled": True}, + "S3 Storage": {"configured": True, "enabled": True}, } result = get_configured_services_from_validator() @@ -186,7 +301,7 @@ class TestGetConfiguredServicesFromValidator: def test_handles_missing_providers(self, mock_get_status): """Test handling when some providers are not in status.""" mock_get_status.return_value = { - "Dropbox": {"configured": True}, + "Dropbox": {"configured": True, "enabled": True}, } result = get_configured_services_from_validator() @@ -194,6 +309,30 @@ class TestGetConfiguredServicesFromValidator: assert result["dropbox"] is True # Other services not in result + @patch("app.tasks.send_to_all.get_provider_status") + def test_configured_but_disabled_service_not_active(self, mock_get_status): + """Test that a configured but disabled service is not returned as active.""" + mock_get_status.return_value = { + "Dropbox": {"configured": True, "enabled": False}, + "S3 Storage": {"configured": True, "enabled": True}, + } + + result = get_configured_services_from_validator() + + assert result["dropbox"] is False + assert result["s3"] is True + + @patch("app.tasks.send_to_all.get_provider_status") + def test_missing_enabled_field_defaults_to_true_for_backward_compatibility(self, mock_get_status): + """Test that missing 'enabled' key defaults to True (backward compatible).""" + mock_get_status.return_value = { + "Dropbox": {"configured": True}, + } + + result = get_configured_services_from_validator() + + assert result["dropbox"] is True + @pytest.mark.unit class TestSendToAllDestinations: