mirror of
https://github.com/langgenius/dify.git
synced 2026-10-04 13:47:55 +08:00
test: centralize app and account config overrides (#40860)
This commit is contained in:
1 parent
bc845ea748
commit
f3bb73b671
11 files changed
+273
-256
No files matched your search
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import builtins
|
||||
import json
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
from importlib import util
|
||||
from pathlib import Path
|
||||
@@ -17,7 +18,6 @@ from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.orm import Session
|
||||
from werkzeug.datastructures import MultiDict
|
||||
|
||||
from configs import dify_config
|
||||
from models.model import App, AppMode, IconType
|
||||
from models.workflow import Workflow, WorkflowType
|
||||
|
||||
@@ -594,7 +594,10 @@ def test_app_list_uses_injected_session_for_draft_workflows(
|
||||
assert response["data"][0]["permission_keys"] == ["app.acl.edit"]
|
||||
|
||||
|
||||
def test_app_create_api_attaches_permission_keys(app, app_module, unbound_session: Session):
|
||||
def test_app_create_api_attaches_permission_keys(
|
||||
app, app_module, unbound_session: Session, config_overrides: Callable[..., None]
|
||||
):
|
||||
config_overrides(RBAC_ENABLED=True)
|
||||
method = app_module.AppListApi.post
|
||||
while hasattr(method, "__wrapped__"):
|
||||
method = method.__wrapped__
|
||||
@@ -611,7 +614,6 @@ def test_app_create_api_attaches_permission_keys(app, app_module, unbound_sessio
|
||||
|
||||
with app.test_request_context("/apps", method="POST", json={}):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.dify_config, "RBAC_ENABLED", True)
|
||||
app_module.console_ns.payload = {
|
||||
"name": "Created App",
|
||||
"description": "Summary",
|
||||
@@ -658,7 +660,10 @@ def test_app_create_api_attaches_permission_keys(app, app_module, unbound_sessio
|
||||
initialize_rbac_task.delay.assert_called_once_with("tenant-1", "acct-1", app_id="app-new")
|
||||
|
||||
|
||||
def test_app_list_api_attaches_permission_keys(app, app_module, sqlite_session: Session):
|
||||
def test_app_list_api_attaches_permission_keys(
|
||||
app, app_module, sqlite_session: Session, config_overrides: Callable[..., None]
|
||||
):
|
||||
config_overrides(RBAC_ENABLED=True)
|
||||
method = app_module.AppListApi.get
|
||||
while hasattr(method, "__wrapped__"):
|
||||
method = method.__wrapped__
|
||||
@@ -678,7 +683,6 @@ def test_app_list_api_attaches_permission_keys(app, app_module, sqlite_session:
|
||||
|
||||
with app.test_request_context("/apps"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(dify_config, "RBAC_ENABLED", True)
|
||||
monkeypatch.setattr(
|
||||
app_module.AppService,
|
||||
"get_paginate_apps",
|
||||
@@ -719,7 +723,13 @@ def test_app_list_api_attaches_permission_keys(app, app_module, sqlite_session:
|
||||
assert resp["data"][0]["permission_keys"] == ["app.acl.view_layout", "app.acl.edit"]
|
||||
|
||||
|
||||
def test_recent_app_list_api_returns_only_home_card_fields(app, app_module, unbound_session: Session):
|
||||
def test_recent_app_list_api_returns_only_home_card_fields(
|
||||
app,
|
||||
app_module,
|
||||
unbound_session: Session,
|
||||
config_overrides: Callable[..., None],
|
||||
):
|
||||
config_overrides(RBAC_ENABLED=False)
|
||||
method = app_module.RecentAppListApi.get
|
||||
while hasattr(method, "__wrapped__"):
|
||||
method = method.__wrapped__
|
||||
@@ -739,7 +749,6 @@ def test_recent_app_list_api_returns_only_home_card_fields(app, app_module, unbo
|
||||
|
||||
with app.test_request_context("/apps/recent?limit=8"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(dify_config, "RBAC_ENABLED", False)
|
||||
monkeypatch.setattr(app_module.AppService, "get_recent_apps", get_recent_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
@@ -797,7 +806,10 @@ def test_recent_app_response_rejects_non_home_app_modes(app_module, mode: str) -
|
||||
)
|
||||
|
||||
|
||||
def test_recent_app_list_api_applies_rbac_visibility_filter(app, app_module, unbound_session: Session):
|
||||
def test_recent_app_list_api_applies_rbac_visibility_filter(
|
||||
app, app_module, unbound_session: Session, config_overrides: Callable[..., None]
|
||||
):
|
||||
config_overrides(RBAC_ENABLED=True)
|
||||
method = app_module.RecentAppListApi.get
|
||||
while hasattr(method, "__wrapped__"):
|
||||
method = method.__wrapped__
|
||||
@@ -805,7 +817,6 @@ def test_recent_app_list_api_applies_rbac_visibility_filter(app, app_module, unb
|
||||
get_recent_apps = MagicMock(return_value=[])
|
||||
with app.test_request_context("/apps/recent"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(dify_config, "RBAC_ENABLED", True)
|
||||
monkeypatch.setattr(app_module.AppService, "get_recent_apps", get_recent_apps)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
@@ -835,8 +846,9 @@ def test_recent_app_list_api_applies_rbac_visibility_filter(app, app_module, unb
|
||||
|
||||
|
||||
def test_app_list_api_limits_to_apps_created_by_current_user_without_view_permission(
|
||||
app, app_module, unbound_session: Session
|
||||
app, app_module, unbound_session: Session, config_overrides: Callable[..., None]
|
||||
):
|
||||
config_overrides(RBAC_ENABLED=True)
|
||||
method = app_module.AppListApi.get
|
||||
while hasattr(method, "__wrapped__"):
|
||||
method = method.__wrapped__
|
||||
@@ -847,7 +859,6 @@ def test_app_list_api_limits_to_apps_created_by_current_user_without_view_permis
|
||||
with app.test_request_context("/apps"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_paginate_apps", get_paginate_apps)
|
||||
monkeypatch.setattr(app_module.dify_config, "RBAC_ENABLED", True)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
@@ -879,8 +890,9 @@ def test_app_list_api_limits_to_apps_created_by_current_user_without_view_permis
|
||||
|
||||
|
||||
def test_app_list_api_limits_to_preview_overrides_without_manage_own_permission(
|
||||
app, app_module, unbound_session: Session
|
||||
app, app_module, unbound_session: Session, config_overrides: Callable[..., None]
|
||||
):
|
||||
config_overrides(RBAC_ENABLED=True)
|
||||
method = app_module.AppListApi.get
|
||||
while hasattr(method, "__wrapped__"):
|
||||
method = method.__wrapped__
|
||||
@@ -891,7 +903,6 @@ def test_app_list_api_limits_to_preview_overrides_without_manage_own_permission(
|
||||
with app.test_request_context("/apps"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_paginate_apps", get_paginate_apps)
|
||||
monkeypatch.setattr(app_module.dify_config, "RBAC_ENABLED", True)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
@@ -936,8 +947,9 @@ def test_app_list_api_limits_to_preview_overrides_without_manage_own_permission(
|
||||
|
||||
|
||||
def test_app_list_api_returns_no_apps_without_workspace_or_resource_view_permission(
|
||||
app, app_module, unbound_session: Session
|
||||
app, app_module, unbound_session: Session, config_overrides: Callable[..., None]
|
||||
):
|
||||
config_overrides(RBAC_ENABLED=True)
|
||||
method = app_module.AppListApi.get
|
||||
while hasattr(method, "__wrapped__"):
|
||||
method = method.__wrapped__
|
||||
@@ -948,7 +960,6 @@ def test_app_list_api_returns_no_apps_without_workspace_or_resource_view_permiss
|
||||
with app.test_request_context("/apps"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(app_module.AppService, "get_paginate_apps", get_paginate_apps)
|
||||
monkeypatch.setattr(app_module.dify_config, "RBAC_ENABLED", True)
|
||||
monkeypatch.setattr(
|
||||
app_module.enterprise_rbac_service.RBACService.MyPermissions,
|
||||
"get",
|
||||
@@ -973,7 +984,10 @@ def test_app_list_api_returns_no_apps_without_workspace_or_resource_view_permiss
|
||||
assert params.is_created_by_me is None
|
||||
|
||||
|
||||
def test_app_detail_api_attaches_current_user_permission_keys(app, app_module, unbound_session: Session):
|
||||
def test_app_detail_api_attaches_current_user_permission_keys(
|
||||
app, app_module, unbound_session: Session, config_overrides: Callable[..., None]
|
||||
):
|
||||
config_overrides(RBAC_ENABLED=True)
|
||||
method = app_module.AppApi.get
|
||||
while hasattr(method, "__wrapped__"):
|
||||
method = method.__wrapped__
|
||||
@@ -990,7 +1004,6 @@ def test_app_detail_api_attaches_current_user_permission_keys(app, app_module, u
|
||||
|
||||
with app.test_request_context("/apps/app-1"):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(dify_config, "RBAC_ENABLED", True)
|
||||
get_app = MagicMock(return_value=app_obj)
|
||||
monkeypatch.setattr(app_module, "AppService", lambda: SimpleNamespace(get_app=get_app))
|
||||
monkeypatch.setattr(
|
||||
@@ -1039,7 +1052,14 @@ def test_app_detail_api_attaches_current_user_permission_keys(app, app_module, u
|
||||
]
|
||||
|
||||
|
||||
def test_app_copy_api_attaches_permission_keys(app, app_module, sqlite_session: Session, sqlite_engine: Engine):
|
||||
def test_app_copy_api_attaches_permission_keys(
|
||||
app,
|
||||
app_module,
|
||||
sqlite_session: Session,
|
||||
sqlite_engine: Engine,
|
||||
config_overrides: Callable[..., None],
|
||||
):
|
||||
config_overrides(RBAC_ENABLED=True)
|
||||
method = app_module.AppCopyApi.post
|
||||
while hasattr(method, "__wrapped__"):
|
||||
method = method.__wrapped__
|
||||
@@ -1063,7 +1083,6 @@ def test_app_copy_api_attaches_permission_keys(app, app_module, sqlite_session:
|
||||
|
||||
with app.test_request_context("/apps/app-original/copy", method="POST", json={}):
|
||||
with pytest.MonkeyPatch.context() as monkeypatch:
|
||||
monkeypatch.setattr(dify_config, "RBAC_ENABLED", True)
|
||||
monkeypatch.setattr(
|
||||
app_module,
|
||||
"AppDslService",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -24,6 +25,11 @@ from services.errors.account import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _cloud_edition(config_overrides: Callable[..., None]) -> None:
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
|
||||
|
||||
class TestEmailRegisterSendEmailApi:
|
||||
@patch("controllers.console.auth.email_register.AccountService.get_account_by_email_with_case_fallback")
|
||||
@patch("controllers.console.auth.email_register.AccountService.send_email_register_email")
|
||||
@@ -50,8 +56,6 @@ class TestEmailRegisterSendEmailApi:
|
||||
is_allow_register=True,
|
||||
)
|
||||
with (
|
||||
patch("controllers.console.auth.email_register.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
patch("controllers.console.wraps.FeatureService.get_system_features", return_value=feature_flags),
|
||||
):
|
||||
with app.test_request_context(
|
||||
@@ -138,7 +142,6 @@ class TestEmailRegisterCheckApi:
|
||||
is_allow_register=True,
|
||||
)
|
||||
with (
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
patch("controllers.console.wraps.FeatureService.get_system_features", return_value=feature_flags),
|
||||
):
|
||||
with app.test_request_context(
|
||||
@@ -212,7 +215,6 @@ class TestEmailRegisterResetApi:
|
||||
is_allow_register=True,
|
||||
)
|
||||
with (
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
patch("controllers.console.wraps.FeatureService.get_system_features", return_value=feature_flags),
|
||||
):
|
||||
with app.test_request_context(
|
||||
@@ -265,7 +267,6 @@ class TestEmailRegisterResetApi:
|
||||
is_allow_register=True,
|
||||
)
|
||||
with (
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
patch("controllers.console.wraps.FeatureService.get_system_features", return_value=feature_flags),
|
||||
):
|
||||
with app.test_request_context(
|
||||
@@ -323,7 +324,6 @@ class TestEmailRegisterResetApi:
|
||||
is_allow_register=True,
|
||||
)
|
||||
with (
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
patch("controllers.console.wraps.FeatureService.get_system_features", return_value=feature_flags),
|
||||
):
|
||||
with app.test_request_context(
|
||||
|
||||
@@ -9,6 +9,7 @@ This module tests the email code login mechanism including:
|
||||
"""
|
||||
|
||||
import base64
|
||||
from collections.abc import Callable
|
||||
from unittest.mock import ANY, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -56,6 +57,11 @@ from services.turnstile_service import TurnstileChallengeRejectedError, Turnstil
|
||||
TEST_TOKEN = "00000000-0000-4000-8000-000000000001"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _default_edition(config_overrides: Callable[..., None]) -> None:
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY)
|
||||
|
||||
|
||||
def encode_code(code: str) -> str:
|
||||
"""Helper to encode verification code as Base64 for testing."""
|
||||
return base64.b64encode(code.encode("utf-8")).decode()
|
||||
@@ -197,7 +203,14 @@ class TestEmailCodeLoginSendEmailApi:
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.AccountService.is_email_send_ip_limit")
|
||||
@patch("controllers.console.auth.login.TurnstileService.verify")
|
||||
def test_send_email_code_ip_rate_limited(self, mock_verify, mock_is_ip_limit, mock_db, app: Flask):
|
||||
def test_send_email_code_ip_rate_limited(
|
||||
self,
|
||||
mock_verify,
|
||||
mock_is_ip_limit,
|
||||
mock_db,
|
||||
app: Flask,
|
||||
config_overrides: Callable[..., None],
|
||||
):
|
||||
"""
|
||||
Test email code sending blocked by IP rate limit.
|
||||
|
||||
@@ -205,12 +218,11 @@ class TestEmailCodeLoginSendEmailApi:
|
||||
- EmailSendIpLimitError is raised when IP limit exceeded
|
||||
- Prevents spam and abuse
|
||||
"""
|
||||
# Arrange
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
mock_is_ip_limit.return_value = True
|
||||
|
||||
# Act & Assert
|
||||
with (
|
||||
patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
app.test_request_context("/email-code-login", method="POST", json={"email": "test@example.com"}),
|
||||
):
|
||||
with pytest.raises(EmailSendIpLimitError):
|
||||
@@ -232,11 +244,12 @@ class TestEmailCodeLoginSendEmailApi:
|
||||
mock_db,
|
||||
app: Flask,
|
||||
mock_account,
|
||||
config_overrides: Callable[..., None],
|
||||
):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
mock_get_user.return_value = mock_account
|
||||
|
||||
with (
|
||||
patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
app.test_request_context(
|
||||
"/email-code-login",
|
||||
method="POST",
|
||||
@@ -268,9 +281,10 @@ class TestEmailCodeLoginSendEmailApi:
|
||||
app: Flask,
|
||||
service_error: Exception,
|
||||
http_error: type[Exception],
|
||||
config_overrides: Callable[..., None],
|
||||
):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
with (
|
||||
patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
patch("controllers.console.auth.login.TurnstileService.verify", side_effect=service_error),
|
||||
app.test_request_context(
|
||||
"/email-code-login",
|
||||
@@ -297,11 +311,12 @@ class TestEmailCodeLoginSendEmailApi:
|
||||
mock_db,
|
||||
app: Flask,
|
||||
mock_account,
|
||||
config_overrides: Callable[..., None],
|
||||
):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY)
|
||||
mock_get_user.return_value = mock_account
|
||||
|
||||
with (
|
||||
patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY),
|
||||
app.test_request_context("/email-code-login", method="POST", json={"email": "test@example.com"}),
|
||||
):
|
||||
response = EmailCodeLoginSendEmailApi().post()
|
||||
|
||||
@@ -10,6 +10,7 @@ This module tests the core authentication endpoints including:
|
||||
|
||||
import base64
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
from unittest.mock import ANY, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
@@ -47,6 +48,11 @@ from services.errors.account import (
|
||||
TEST_TOKEN = "00000000-0000-4000-8000-000000000001"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _login_config(config_overrides: Callable[..., None]) -> None:
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY)
|
||||
|
||||
|
||||
def encode_password(password: str) -> str:
|
||||
"""Helper to encode password as Base64 for testing."""
|
||||
return base64.b64encode(password.encode("utf-8")).decode()
|
||||
@@ -98,7 +104,6 @@ class TestLoginApi:
|
||||
return token_pair
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY)
|
||||
@patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit")
|
||||
@patch("controllers.console.auth.login.RegisterService.get_invitation_with_case_fallback")
|
||||
@patch("controllers.console.auth.login.AccountService.authenticate")
|
||||
@@ -149,7 +154,6 @@ class TestLoginApi:
|
||||
assert response.json["result"] == "success"
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY)
|
||||
@patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit")
|
||||
@patch("controllers.console.auth.login.RegisterService.get_invitation_with_case_fallback")
|
||||
@patch("controllers.console.auth.login.AccountService.authenticate")
|
||||
@@ -202,7 +206,6 @@ class TestLoginApi:
|
||||
assert response.json["result"] == "success"
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY)
|
||||
@patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit")
|
||||
@patch("controllers.console.auth.login.RegisterService.get_invitation_with_case_fallback")
|
||||
def test_login_fails_when_rate_limited(
|
||||
@@ -235,10 +238,14 @@ class TestLoginApi:
|
||||
assert warn_records[0].args[1] == LoginFailureReason.LOGIN_RATE_LIMITED
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD)
|
||||
@patch("controllers.console.auth.login.BillingService.get_email_freeze_type")
|
||||
def test_login_fails_when_account_frozen(
|
||||
self, mock_is_frozen, mock_db, app: Flask, caplog: pytest.LogCaptureFixture
|
||||
self,
|
||||
mock_get_freeze_type,
|
||||
mock_db,
|
||||
app: Flask,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
config_overrides: Callable[..., None],
|
||||
):
|
||||
"""
|
||||
Test login rejection for frozen accounts.
|
||||
@@ -248,7 +255,8 @@ class TestLoginApi:
|
||||
- AccountInFreezeError is raised for frozen accounts
|
||||
"""
|
||||
# Arrange
|
||||
mock_is_frozen.return_value = "freeze"
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
mock_get_freeze_type.return_value = "freeze"
|
||||
|
||||
# Act & Assert
|
||||
with app.test_request_context(
|
||||
@@ -266,9 +274,15 @@ class TestLoginApi:
|
||||
assert warn_records[0].args[1] == LoginFailureReason.ACCOUNT_IN_FREEZE
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD)
|
||||
@patch("controllers.console.auth.login.BillingService.get_email_freeze_type")
|
||||
def test_login_fails_when_email_domain_is_suspended(self, mock_get_freeze_type, mock_db, app: Flask):
|
||||
def test_login_fails_when_email_domain_is_suspended(
|
||||
self,
|
||||
mock_get_freeze_type,
|
||||
mock_db,
|
||||
app: Flask,
|
||||
config_overrides: Callable[..., None],
|
||||
):
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
mock_get_freeze_type.return_value = "email_domain_suspended"
|
||||
|
||||
with app.test_request_context(
|
||||
@@ -376,7 +390,6 @@ class TestLoginApi:
|
||||
ResetPasswordSendEmailApi().post()
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY)
|
||||
@patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit")
|
||||
@patch("controllers.console.auth.login.RegisterService.get_invitation_with_case_fallback")
|
||||
@patch("controllers.console.auth.login.AccountService.authenticate")
|
||||
@@ -423,7 +436,6 @@ class TestLoginApi:
|
||||
assert warn_records[0].args[1] == LoginFailureReason.INVALID_CREDENTIALS
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY)
|
||||
@patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit")
|
||||
@patch("controllers.console.auth.login.RegisterService.get_invitation_with_case_fallback")
|
||||
@patch("controllers.console.auth.login.AccountService.authenticate")
|
||||
@@ -460,7 +472,6 @@ class TestLoginApi:
|
||||
assert warn_records[0].args[1] == LoginFailureReason.ACCOUNT_BANNED
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY)
|
||||
@patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit")
|
||||
@patch("controllers.console.auth.login.RegisterService.get_invitation_with_case_fallback")
|
||||
@patch("controllers.console.auth.login.AccountService.authenticate")
|
||||
@@ -504,7 +515,6 @@ class TestLoginApi:
|
||||
login_api.post()
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY)
|
||||
@patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit")
|
||||
@patch("controllers.console.auth.login.RegisterService.get_invitation_with_case_fallback")
|
||||
def test_login_invitation_email_mismatch(self, mock_get_invitation, mock_is_rate_limit, mock_db, app: Flask):
|
||||
@@ -534,7 +544,6 @@ class TestLoginApi:
|
||||
login_api.post()
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY)
|
||||
@patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit")
|
||||
@patch("controllers.console.auth.login.RegisterService.get_invitation_with_case_fallback")
|
||||
@patch("controllers.console.auth.login.AccountService.authenticate")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from collections.abc import Callable
|
||||
from types import SimpleNamespace
|
||||
from typing import override
|
||||
from unittest.mock import MagicMock, patch
|
||||
@@ -52,6 +53,15 @@ def reset_setup_required_cache():
|
||||
_is_setup_completed.reset_success()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _wraps_config(config_overrides: Callable[..., None]) -> None:
|
||||
config_overrides(
|
||||
RBAC_ENABLED=True,
|
||||
DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY,
|
||||
INIT_PASSWORD="",
|
||||
)
|
||||
|
||||
|
||||
class MockUser(UserMixin):
|
||||
"""Simple User class for testing."""
|
||||
|
||||
@@ -399,7 +409,6 @@ class TestRbacPermissionRequired:
|
||||
return "ok"
|
||||
|
||||
with (
|
||||
patch("controllers.common.wraps.dify_config.RBAC_ENABLED", True),
|
||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(current_user, "tenant-1")),
|
||||
patch("controllers.common.wraps._extract_resource_id", return_value="app-123") as mock_extract,
|
||||
patch("controllers.common.wraps._is_resource_owned_by_current_user", return_value=False) as mock_owned,
|
||||
@@ -427,7 +436,6 @@ class TestRbacPermissionRequired:
|
||||
return "ok"
|
||||
|
||||
with (
|
||||
patch("controllers.common.wraps.dify_config.RBAC_ENABLED", True),
|
||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(current_user, "tenant-2")),
|
||||
patch("controllers.common.wraps._extract_resource_id") as mock_extract,
|
||||
patch("controllers.common.wraps._is_resource_owned_by_current_user", return_value=False) as mock_owned,
|
||||
@@ -455,7 +463,6 @@ class TestRbacPermissionRequired:
|
||||
return "ok"
|
||||
|
||||
with (
|
||||
patch("controllers.common.wraps.dify_config.RBAC_ENABLED", True),
|
||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(current_user, "tenant-3")),
|
||||
patch("controllers.common.wraps.RBACService.CheckAccess.check", return_value=True) as mock_check,
|
||||
):
|
||||
@@ -477,7 +484,6 @@ class TestRbacPermissionRequired:
|
||||
return "ok"
|
||||
|
||||
with (
|
||||
patch("controllers.common.wraps.dify_config.RBAC_ENABLED", True),
|
||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(current_user, "tenant-4")),
|
||||
patch("controllers.common.wraps._extract_resource_id", return_value="app-123"),
|
||||
patch("controllers.common.wraps._is_resource_owned_by_current_user", return_value=True) as mock_owned,
|
||||
@@ -496,7 +502,6 @@ class TestRbacPermissionRequired:
|
||||
return "ok"
|
||||
|
||||
with (
|
||||
patch("controllers.common.wraps.dify_config.RBAC_ENABLED", True),
|
||||
patch("controllers.common.wraps.current_account_with_tenant", return_value=(current_user, "tenant-5")),
|
||||
patch("controllers.common.wraps._extract_resource_id", return_value="dataset-123"),
|
||||
patch("controllers.common.wraps._is_resource_owned_by_current_user", return_value=True) as mock_owned,
|
||||
@@ -610,8 +615,7 @@ class TestRbacPermissionRequired:
|
||||
def protected_view():
|
||||
return "ok"
|
||||
|
||||
with patch("controllers.console.wraps.dify_config.RBAC_ENABLED", True):
|
||||
assert protected_view() == "ok"
|
||||
assert protected_view() == "ok"
|
||||
|
||||
|
||||
class TestModelValidationInjection:
|
||||
@@ -668,7 +672,7 @@ class TestModelValidationInjection:
|
||||
class TestEditionChecks:
|
||||
"""Test edition-specific decorators"""
|
||||
|
||||
def test_only_edition_cloud_allows_cloud_edition(self):
|
||||
def test_only_edition_cloud_allows_cloud_edition(self, config_overrides: Callable[..., None]):
|
||||
"""Test cloud edition decorator allows CLOUD edition"""
|
||||
|
||||
# Arrange
|
||||
@@ -676,9 +680,8 @@ class TestEditionChecks:
|
||||
def cloud_view():
|
||||
return "cloud_success"
|
||||
|
||||
# Act
|
||||
with patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD):
|
||||
result = cloud_view()
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
result = cloud_view()
|
||||
|
||||
# Assert
|
||||
assert result == "cloud_success"
|
||||
@@ -694,12 +697,11 @@ class TestEditionChecks:
|
||||
|
||||
# Act & Assert
|
||||
with app.test_request_context():
|
||||
with patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
cloud_view()
|
||||
assert exc_info.value.code == 404
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
cloud_view()
|
||||
assert exc_info.value.code == 404
|
||||
|
||||
def test_only_edition_enterprise_allows_enterprise_edition(self):
|
||||
def test_only_edition_enterprise_allows_enterprise_edition(self, config_overrides: Callable[..., None]):
|
||||
"""Test enterprise edition decorator allows the ENTERPRISE edition."""
|
||||
|
||||
# Arrange
|
||||
@@ -707,9 +709,8 @@ class TestEditionChecks:
|
||||
def enterprise_view():
|
||||
return "enterprise_success"
|
||||
|
||||
# Act
|
||||
with patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.ENTERPRISE):
|
||||
result = enterprise_view()
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE)
|
||||
result = enterprise_view()
|
||||
|
||||
# Assert
|
||||
assert result == "enterprise_success"
|
||||
@@ -722,9 +723,7 @@ class TestEditionChecks:
|
||||
def self_hosted_view():
|
||||
return "self_hosted_success"
|
||||
|
||||
# Act
|
||||
with patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY):
|
||||
result = self_hosted_view()
|
||||
result = self_hosted_view()
|
||||
|
||||
# Assert
|
||||
assert result == "self_hosted_success"
|
||||
@@ -805,8 +804,9 @@ class TestBillingResourceLimits:
|
||||
assert result == "member_added"
|
||||
get_features.assert_called_once_with("tenant123", exclude_vector_space=True)
|
||||
|
||||
def test_should_load_vector_space_from_dedicated_quota_api(self):
|
||||
def test_should_load_vector_space_from_dedicated_quota_api(self, config_overrides: Callable[..., None]):
|
||||
"""Test vector-space limit checks avoid loading the full feature payload."""
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
# Arrange
|
||||
mock_vector_space = MagicMock()
|
||||
mock_vector_space.limit = 10
|
||||
@@ -821,7 +821,6 @@ class TestBillingResourceLimits:
|
||||
"controllers.console.wraps.current_account_with_tenant", return_value=(MockUser("test_user"), "tenant123")
|
||||
):
|
||||
with (
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
patch(
|
||||
"controllers.console.wraps.FeatureService.get_vector_space", return_value=mock_vector_space
|
||||
) as get_vector_space,
|
||||
@@ -984,8 +983,9 @@ class TestRateLimiting:
|
||||
class TestCloudUtmRecord:
|
||||
"""Test cloud UTM recording decorator."""
|
||||
|
||||
def test_should_record_utm_for_cloud_edition_and_cookie(self):
|
||||
def test_should_record_utm_for_cloud_edition_and_cookie(self, config_overrides: Callable[..., None]):
|
||||
"""Test Cloud UTM recording without loading tenant features."""
|
||||
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
|
||||
app = create_app_with_login()
|
||||
|
||||
@cloud_utm_record
|
||||
@@ -994,7 +994,6 @@ class TestCloudUtmRecord:
|
||||
|
||||
with app.test_request_context("/", headers={"Cookie": "utm_info={}"}):
|
||||
with (
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD),
|
||||
patch("controllers.console.wraps.current_account_with_tenant", return_value=(MockUser("u1"), "t1")),
|
||||
patch("controllers.console.wraps.OperationService.record_utm") as record_utm,
|
||||
patch("controllers.console.wraps.FeatureService.get_features") as get_features,
|
||||
@@ -1015,7 +1014,6 @@ class TestCloudUtmRecord:
|
||||
|
||||
with app.test_request_context("/", headers={"Cookie": "utm_info={}"}):
|
||||
with (
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY),
|
||||
patch("controllers.console.wraps.current_account_with_tenant") as current_account,
|
||||
patch("controllers.console.wraps.OperationService.record_utm") as record_utm,
|
||||
patch("controllers.console.wraps.FeatureService.get_features") as get_features,
|
||||
@@ -1047,10 +1045,7 @@ class TestSystemSetup:
|
||||
return "admin_success"
|
||||
|
||||
# Act
|
||||
with (
|
||||
patch("controllers.console.wraps.db.session", sqlite_session),
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY),
|
||||
):
|
||||
with patch("controllers.console.wraps.db.session", sqlite_session):
|
||||
result = admin_view()
|
||||
|
||||
# Assert
|
||||
@@ -1064,10 +1059,7 @@ class TestSystemSetup:
|
||||
def admin_view():
|
||||
return "admin_success"
|
||||
|
||||
with (
|
||||
patch("controllers.console.wraps.db.session", sqlite_session),
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY),
|
||||
):
|
||||
with patch("controllers.console.wraps.db.session", sqlite_session):
|
||||
assert admin_view() == "admin_success"
|
||||
sqlite_session.delete(setup)
|
||||
sqlite_session.commit()
|
||||
@@ -1084,27 +1076,23 @@ class TestSystemSetup:
|
||||
|
||||
with (
|
||||
patch("controllers.console.wraps.db.session", sqlite_session),
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY),
|
||||
patch("controllers.console.wraps.dify_config.INIT_PASSWORD", ""),
|
||||
):
|
||||
with pytest.raises(NotSetupError):
|
||||
admin_view()
|
||||
self._complete_setup(sqlite_session)
|
||||
assert admin_view() == "admin_success"
|
||||
|
||||
def test_should_raise_not_init_validate_error_with_init_password(self, sqlite_session: Session):
|
||||
def test_should_raise_not_init_validate_error_with_init_password(
|
||||
self, sqlite_session: Session, config_overrides: Callable[..., None]
|
||||
):
|
||||
"""Test NotInitValidateError when INIT_PASSWORD is set but setup not complete"""
|
||||
|
||||
@setup_required
|
||||
def admin_view():
|
||||
return "admin_success"
|
||||
|
||||
# Act & Assert
|
||||
with (
|
||||
patch("controllers.console.wraps.db.session", sqlite_session),
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY),
|
||||
patch("controllers.console.wraps.dify_config.INIT_PASSWORD", "some_password"),
|
||||
):
|
||||
config_overrides(INIT_PASSWORD="some_password")
|
||||
with patch("controllers.console.wraps.db.session", sqlite_session):
|
||||
with pytest.raises(NotInitValidateError):
|
||||
admin_view()
|
||||
|
||||
@@ -1116,11 +1104,7 @@ class TestSystemSetup:
|
||||
return "admin_success"
|
||||
|
||||
# Act & Assert
|
||||
with (
|
||||
patch("controllers.console.wraps.db.session", sqlite_session),
|
||||
patch("controllers.console.wraps.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY),
|
||||
patch("controllers.console.wraps.dify_config.INIT_PASSWORD", ""),
|
||||
):
|
||||
with patch("controllers.console.wraps.db.session", sqlite_session):
|
||||
with pytest.raises(NotSetupError):
|
||||
admin_view()
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@@ -24,6 +25,11 @@ from models.model import App
|
||||
from services.enterprise.enterprise_service import WebAppAccessMode
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _rbac_config(config_overrides: Callable[..., None]) -> None:
|
||||
config_overrides(RBAC_ENABLED=True)
|
||||
|
||||
|
||||
def _data(**kwargs) -> AuthData:
|
||||
defaults: dict = {"token_type": TokenType.OAUTH_ACCOUNT, "token_hash": "hash", "scopes": frozenset({Scope.FULL})}
|
||||
defaults.update(kwargs)
|
||||
@@ -88,18 +94,15 @@ def test_check_rbac_noop_when_no_requirement():
|
||||
mock_enforce.assert_not_called()
|
||||
|
||||
|
||||
def test_check_rbac_noop_when_rbac_disabled():
|
||||
with (
|
||||
patch("controllers.openapi.auth.verify.dify_config.RBAC_ENABLED", False),
|
||||
patch("controllers.openapi.auth.verify.enforce_rbac_access") as mock_enforce,
|
||||
):
|
||||
def test_check_rbac_noop_when_rbac_disabled(config_overrides: Callable[..., None]):
|
||||
config_overrides(RBAC_ENABLED=False)
|
||||
with patch("controllers.openapi.auth.verify.enforce_rbac_access") as mock_enforce:
|
||||
check_rbac_permission(_data(rbac=_RBAC_REQ, caller_kind="account"))
|
||||
mock_enforce.assert_not_called()
|
||||
|
||||
|
||||
def test_check_rbac_skips_end_user_caller():
|
||||
with (
|
||||
patch("controllers.openapi.auth.verify.dify_config.RBAC_ENABLED", True),
|
||||
patch("controllers.openapi.auth.verify.enforce_rbac_access") as mock_enforce,
|
||||
):
|
||||
check_rbac_permission(_data(rbac=_RBAC_REQ, caller_kind="end_user"))
|
||||
@@ -107,9 +110,8 @@ def test_check_rbac_skips_end_user_caller():
|
||||
|
||||
|
||||
def test_check_rbac_raises_when_context_missing():
|
||||
with patch("controllers.openapi.auth.verify.dify_config.RBAC_ENABLED", True):
|
||||
with pytest.raises(Forbidden, match="rbac context missing"):
|
||||
check_rbac_permission(_data(rbac=_RBAC_REQ, caller_kind="account", account_id=None, tenant=None))
|
||||
with pytest.raises(Forbidden, match="rbac context missing"):
|
||||
check_rbac_permission(_data(rbac=_RBAC_REQ, caller_kind="account", account_id=None, tenant=None))
|
||||
|
||||
|
||||
def test_check_rbac_enforces_for_account_caller():
|
||||
@@ -124,7 +126,6 @@ def test_check_rbac_enforces_for_account_caller():
|
||||
path_params={"app_id": "app-1"},
|
||||
)
|
||||
with (
|
||||
patch("controllers.openapi.auth.verify.dify_config.RBAC_ENABLED", True),
|
||||
patch("controllers.openapi.auth.verify.enforce_rbac_access") as mock_enforce,
|
||||
):
|
||||
check_rbac_permission(data)
|
||||
|
||||
Reference in new issue
Block a user