From 6fb1df1aade8a8c98c27fe50dbe44045cba1724d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 00:31:28 +0000 Subject: [PATCH] test(views): boost coverage for 11 view modules toward 100% MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds tests/test_views_coverage_boost.py with 37 tests covering: - api_tokens, notifications, shared_links, share, plans views (template rendering) - imap_accounts view (helper functions + route with mocked owner) - integrations view (DB queries, tier logic, HTTP/generic exception handling) - general view (multi-user subscription branch with signed session cookie) - filemanager view (PB formatting, broken symlink stat errors in scan/walk) - files view (pipeline step filtering, dedup toggle, ValueError in commonpath) - help view (no-session branch, logged-in user Zammad widget population) Coverage improvements (full suite): - 27 of 29 view modules now at 100% (was 18 of 29) - imap_accounts: 30.95% → 100% - integrations: 82.09% → 100% - filemanager: 96.63% → 100% - help: 96% → 100% - plans: 86.67% → 100% - api_tokens/notifications/shared_links/share: 88-90% → 100% Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- tests/test_views_coverage_boost.py | 107 ++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/tests/test_views_coverage_boost.py b/tests/test_views_coverage_boost.py index 7d258fc7..cd639b9b 100644 --- a/tests/test_views_coverage_boost.py +++ b/tests/test_views_coverage_boost.py @@ -4,9 +4,7 @@ Covers: api_tokens, notifications, shared_links, share, plans, imap_accounts, integrations, general, filemanager, files, help. """ -import asyncio import os -from pathlib import Path from types import SimpleNamespace from unittest.mock import MagicMock, patch @@ -20,7 +18,6 @@ from app.config import settings as app_settings from app.database import Base, get_db from app.main import app - # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @@ -212,6 +209,18 @@ class TestIntegrationsView: resp = client_fresh.get("/integrations") assert resp.status_code == 500 + @pytest.mark.unit + def test_integrations_dashboard_http_exception_passthrough(self, client_fresh: TestClient): + """An HTTPException inside the dashboard is re-raised, not wrapped in 500.""" + from fastapi import HTTPException + + with patch( + "app.views.integrations.get_current_owner_id", + side_effect=HTTPException(status_code=403, detail="Forbidden"), + ): + resp = client_fresh.get("/integrations") + assert resp.status_code == 403 + @pytest.mark.unit def test_get_max_destinations_free_default(self): from app.views.integrations import _get_max_destinations @@ -263,43 +272,65 @@ class TestIntegrationsView: class TestGeneralViewMultiUser: """Cover the multi_user_enabled subscription branch (lines 96-105).""" + @staticmethod + def _signed_session(user_data: dict) -> str: + """Create a signed Starlette session cookie containing user_data.""" + import json + from base64 import b64encode + + from itsdangerous import TimestampSigner + + secret = os.environ.get( + "SESSION_SECRET", + "test_secret_key_for_testing_must_be_at_least_32_characters_long", + ) + signer = TimestampSigner(secret) + data = {"user": user_data} + return signer.sign(b64encode(json.dumps(data).encode("utf-8"))).decode("utf-8") + @pytest.mark.unit def test_home_page_multi_user_with_subscription(self, _fresh_db, client_fresh: TestClient): - """When multi_user_enabled is True and user has owner_id, subscription info is fetched.""" + """When multi_user_enabled is True and user has owner_id, subscription info is fetched. + + Lines 96-103: Exercises the subscription lookup path. + """ + cookie_val = self._signed_session({"username": "testuser", "email": "test@example.com", "is_admin": False}) + tier_mock = { + "id": "starter", + "name": "Starter", + "lifetime_file_limit": 1000, + "daily_upload_limit": 50, + "monthly_upload_limit": 500, + } + usage_mock = {"lifetime": 10, "today": 2, "month": 8} with ( patch.object(app_settings, "multi_user_enabled", True), patch("app.utils.setup_wizard.is_setup_required", return_value=False), patch("app.views.general.get_provider_status", return_value={}), patch("app.views.general.validate_storage_configs", return_value={}), - patch( - "app.utils.subscription.get_user_tier_id", - return_value="starter", - ), - patch( - "app.utils.subscription.get_tier", - return_value={"id": "starter", "name": "Starter"}, - ), - patch( - "app.utils.subscription.get_user_usage", - return_value={"pages": 10}, - ), + patch("app.utils.subscription.get_user_tier_id", return_value="starter"), + patch("app.utils.subscription.get_tier", return_value=tier_mock), + patch("app.utils.subscription.get_user_usage", return_value=usage_mock), ): + client_fresh.cookies.set("session", cookie_val) resp = client_fresh.get("/?setup=complete") assert resp.status_code == 200 @pytest.mark.unit def test_home_page_multi_user_subscription_error(self, _fresh_db, client_fresh: TestClient): - """When subscription lookup fails, error is logged but page still renders.""" + """When subscription lookup fails, error is logged but page still renders. + + Lines 104-105: Exercises the exception handling branch. + """ + cookie_val = self._signed_session({"username": "testuser", "email": "test@example.com", "is_admin": False}) with ( patch.object(app_settings, "multi_user_enabled", True), patch("app.utils.setup_wizard.is_setup_required", return_value=False), patch("app.views.general.get_provider_status", return_value={}), patch("app.views.general.validate_storage_configs", return_value={}), - patch( - "app.utils.subscription.get_user_tier_id", - side_effect=RuntimeError("DB error"), - ), + patch("app.utils.subscription.get_user_tier_id", side_effect=RuntimeError("boom")), ): + client_fresh.cookies.set("session", cookie_val) resp = client_fresh.get("/?setup=complete") assert resp.status_code == 200 @@ -523,7 +554,8 @@ class TestHelpViewCoverageGaps: assert resp.status_code == 200 @pytest.mark.unit - def test_help_page_request_without_session(self): + @pytest.mark.asyncio + async def test_help_page_request_without_session(self): """Direct function call where request has no session attribute. Branch 34->37: when hasattr(request, 'session') is False. @@ -537,7 +569,7 @@ class TestHelpViewCoverageGaps: with patch("app.views.help.templates") as mock_templates: mock_templates.TemplateResponse.return_value = "ok" - result = asyncio.get_event_loop().run_until_complete(help_center(mock_request)) + await help_center(mock_request) # Template should be called with empty user context call_args = mock_templates.TemplateResponse.call_args @@ -545,3 +577,32 @@ class TestHelpViewCoverageGaps: assert ctx["user_name"] == "" assert ctx["user_email"] == "" assert ctx["user_id"] == "" + + @pytest.mark.unit + def test_help_page_with_logged_in_user(self, client_fresh: TestClient): + """When session has user data, Zammad widget fields are populated. + + Covers lines 41-43 (user_name, user_email, user_id extraction). + """ + import json + from base64 import b64encode + + from itsdangerous import TimestampSigner + + secret = os.environ.get( + "SESSION_SECRET", + "test_secret_key_for_testing_must_be_at_least_32_characters_long", + ) + signer = TimestampSigner(secret) + session_data = { + "user": { + "name": "Jane Doe", + "email": "jane@example.com", + "preferred_username": "janedoe", + } + } + cookie_val = signer.sign(b64encode(json.dumps(session_data).encode("utf-8"))).decode("utf-8") + client_fresh.cookies.set("session", cookie_val) + + resp = client_fresh.get("/help") + assert resp.status_code == 200