diff --git a/frontend/templates/integrations_dashboard.html b/frontend/templates/integrations_dashboard.html index eb1c2a16..a710b98c 100644 --- a/frontend/templates/integrations_dashboard.html +++ b/frontend/templates/integrations_dashboard.html @@ -1139,16 +1139,18 @@ function integrationsDashboard() { typeLabel(t) { return TYPE_LABELS[t] || t; }, typeIcon(t) { return TYPE_ICONS[t] || 'fa-plug text-gray-400'; }, hasFormFields(t) { return TYPES_WITH_FORM_FIELDS.has(t); }, + _watchFolderOAuthSource(intg) { + if (intg.integration_type !== 'WATCH_FOLDER') return null; + const src = ((intg.config || {}).source_type || '').toLowerCase(); + if (src === 'dropbox' || src === 'onedrive' || src === 'google_drive') return src; + return null; + }, + isOAuthType(intg) { // Direct OAuth types: DROPBOX, GOOGLE_DRIVE, ONEDRIVE if (OAUTH_TYPES.has(intg.integration_type)) return true; - // WATCH_FOLDER with an OAuth-backed source type (dropbox, onedrive, google_drive) - if (intg.integration_type === 'WATCH_FOLDER') { - const cfg = intg.config || {}; - const src = (cfg.source_type || '').toLowerCase(); - return src === 'dropbox' || src === 'onedrive' || src === 'google_drive'; - } - return false; + // WATCH_FOLDER with an OAuth-backed source type + return this._watchFolderOAuthSource(intg) !== null; }, oauthLink(intg) { @@ -1156,13 +1158,10 @@ function integrationsDashboard() { if (t === 'DROPBOX') return '/dropbox-setup'; if (t === 'GOOGLE_DRIVE') return '/google-drive-setup'; if (t === 'ONEDRIVE') return '/onedrive-setup'; - if (t === 'WATCH_FOLDER') { - const cfg = intg.config || {}; - const src = (cfg.source_type || '').toLowerCase(); - if (src === 'dropbox') return '/dropbox-setup'; - if (src === 'onedrive') return '/onedrive-setup'; - if (src === 'google_drive') return '/google-drive-setup'; - } + const src = this._watchFolderOAuthSource(intg); + if (src === 'dropbox') return '/dropbox-setup'; + if (src === 'onedrive') return '/onedrive-setup'; + if (src === 'google_drive') return '/google-drive-setup'; return '#'; }, diff --git a/tests/test_views_dropbox.py b/tests/test_views_dropbox.py index a93d3065..8f089cda 100644 --- a/tests/test_views_dropbox.py +++ b/tests/test_views_dropbox.py @@ -1,7 +1,12 @@ """Tests for app/views/dropbox.py module.""" +import json +from unittest.mock import patch + import pytest +from app.models import UserIntegration + @pytest.mark.integration class TestDropboxViews: @@ -44,3 +49,47 @@ class TestDropboxViews: assert "oauth_integration_id" in body # The JS code is always present # But integration_id template var should be empty assert 'const integrationId = ""' in body + + def test_dropbox_setup_user_mode_invalid_json_config(self, client, db_session): + """Test user-mode renders correctly when integration.config is invalid JSON.""" + owner_id = "user_invalid_json@example.com" + integration = UserIntegration( + owner_id=owner_id, + direction="DESTINATION", + integration_type="DROPBOX", + name="My Dropbox (bad cfg)", + config="{INVALID JSON}", + is_active=True, + ) + db_session.add(integration) + db_session.commit() + db_session.refresh(integration) + + with patch("app.views.dropbox.get_current_owner_id", return_value=owner_id): + response = client.get(f"/dropbox-setup?integration_id={integration.id}") + + assert response.status_code == 200 + # Should render user mode without errors despite the bad config + assert b"user_mode" not in response.content or b"Back to Integrations" in response.content + + def test_dropbox_setup_user_mode_valid_config(self, client, db_session): + """Test user-mode correctly loads folder path from integration config.""" + owner_id = "user_valid_cfg@example.com" + integration = UserIntegration( + owner_id=owner_id, + direction="DESTINATION", + integration_type="DROPBOX", + name="My Dropbox", + config=json.dumps({"folder": "/Documents/Uploads"}), + is_active=True, + ) + db_session.add(integration) + db_session.commit() + db_session.refresh(integration) + + with patch("app.views.dropbox.get_current_owner_id", return_value=owner_id): + response = client.get(f"/dropbox-setup?integration_id={integration.id}") + + assert response.status_code == 200 + assert b"/Documents/Uploads" in response.content + assert b"Back to Integrations" in response.content diff --git a/tests/test_views_google_drive.py b/tests/test_views_google_drive.py index 397562d6..78c76321 100644 --- a/tests/test_views_google_drive.py +++ b/tests/test_views_google_drive.py @@ -1,10 +1,13 @@ """Tests for app/views/google_drive.py module.""" +import json import urllib.parse from unittest.mock import patch import pytest +from app.models import UserIntegration + @pytest.mark.integration class TestGoogleDriveViews: @@ -156,3 +159,47 @@ class TestGoogleDriveViews: response = client.get("/google-drive-setup") assert response.status_code == 200 + + def test_google_drive_setup_user_mode_invalid_json_config(self, client, db_session): + """Test user-mode renders correctly when integration.config is invalid JSON.""" + owner_id = "user_gd_invalid_json@example.com" + integration = UserIntegration( + owner_id=owner_id, + direction="DESTINATION", + integration_type="GOOGLE_DRIVE", + name="My GDrive (bad cfg)", + config="{INVALID JSON}", + is_active=True, + ) + db_session.add(integration) + db_session.commit() + db_session.refresh(integration) + + with patch("app.views.google_drive.get_current_owner_id", return_value=owner_id): + response = client.get(f"/google-drive-setup?integration_id={integration.id}") + + assert response.status_code == 200 + # Should render user mode without errors despite the bad config + assert b"Back to Integrations" in response.content + + def test_google_drive_setup_user_mode_valid_config(self, client, db_session): + """Test user-mode correctly loads folder ID from integration config.""" + owner_id = "user_gd_valid_cfg@example.com" + integration = UserIntegration( + owner_id=owner_id, + direction="DESTINATION", + integration_type="GOOGLE_DRIVE", + name="My Google Drive", + config=json.dumps({"folder_id": "1abc2def3ghi"}), + is_active=True, + ) + db_session.add(integration) + db_session.commit() + db_session.refresh(integration) + + with patch("app.views.google_drive.get_current_owner_id", return_value=owner_id): + response = client.get(f"/google-drive-setup?integration_id={integration.id}") + + assert response.status_code == 200 + assert b"1abc2def3ghi" in response.content + assert b"Back to Integrations" in response.content diff --git a/tests/test_views_onedrive.py b/tests/test_views_onedrive.py index aa9fe116..3b8d4502 100644 --- a/tests/test_views_onedrive.py +++ b/tests/test_views_onedrive.py @@ -1,7 +1,12 @@ """Tests for app/views/onedrive.py module.""" +import json +from unittest.mock import patch + import pytest +from app.models import UserIntegration + @pytest.mark.integration class TestOnedriveViews: @@ -42,3 +47,47 @@ class TestOnedriveViews: body = response.text assert "oauth_integration_id" in body assert 'const integrationId = ""' in body + + def test_onedrive_setup_user_mode_invalid_json_config(self, client, db_session): + """Test user-mode renders correctly when integration.config is invalid JSON.""" + owner_id = "user_od_invalid_json@example.com" + integration = UserIntegration( + owner_id=owner_id, + direction="DESTINATION", + integration_type="ONEDRIVE", + name="My OneDrive (bad cfg)", + config="{INVALID JSON}", + is_active=True, + ) + db_session.add(integration) + db_session.commit() + db_session.refresh(integration) + + with patch("app.views.onedrive.get_current_owner_id", return_value=owner_id): + response = client.get(f"/onedrive-setup?integration_id={integration.id}") + + assert response.status_code == 200 + # Should render user mode without errors despite the bad config + assert b"Back to Integrations" in response.content + + def test_onedrive_setup_user_mode_valid_config(self, client, db_session): + """Test user-mode correctly loads folder path from integration config.""" + owner_id = "user_od_valid_cfg@example.com" + integration = UserIntegration( + owner_id=owner_id, + direction="DESTINATION", + integration_type="ONEDRIVE", + name="My OneDrive", + config=json.dumps({"folder_path": "Documents/Archive"}), + is_active=True, + ) + db_session.add(integration) + db_session.commit() + db_session.refresh(integration) + + with patch("app.views.onedrive.get_current_owner_id", return_value=owner_id): + response = client.get(f"/onedrive-setup?integration_id={integration.id}") + + assert response.status_code == 200 + assert b"Documents/Archive" in response.content + assert b"Back to Integrations" in response.content