From 20fdbcf36ff0ad8034a2ea7cd6cd7275f4a768d2 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Tue, 18 Aug 2026 12:01:01 +0000 Subject: [PATCH] test: centralize enterprise config overrides (#40853) --- .../console/test_workspace_members.py | 86 +++++++------ .../console/workspace/test_members.py | 42 +++---- .../console/workspace/test_rbac.py | 40 +++--- .../core/repositories/test_factory.py | 49 +++----- .../enterprise/test_account_deletion_sync.py | 90 ++++++-------- .../enterprise/test_enterprise_service.py | 61 +++------- .../services/enterprise/test_rbac_service.py | 114 ++++++++++-------- .../workflow/test_queue_dispatcher.py | 35 +++--- 8 files changed, 221 insertions(+), 296 deletions(-) diff --git a/api/tests/unit_tests/controllers/console/test_workspace_members.py b/api/tests/unit_tests/controllers/console/test_workspace_members.py index cb7df1644b5..69d05246f2d 100644 --- a/api/tests/unit_tests/controllers/console/test_workspace_members.py +++ b/api/tests/unit_tests/controllers/console/test_workspace_members.py @@ -34,6 +34,14 @@ def _build_feature_flags(): class TestMemberInviteEmailApi: + @pytest.fixture(autouse=True) + def _member_config(self, config_overrides) -> None: + config_overrides( + RBAC_ENABLED=False, + CONSOLE_WEB_URL="https://console.example.com", + DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY, + ) + @pytest.fixture(autouse=True) def _mock_member_invite_lock(self): with patch("controllers.console.workspace.members.redis_client.lock", return_value=nullcontext()): @@ -51,10 +59,7 @@ class TestMemberInviteEmailApi: inviter = SimpleNamespace(email="Owner@Example.com", current_tenant=tenant, status="active") with ( - patch("controllers.console.workspace.members.dify_config.RBAC_ENABLED", False), - patch("controllers.console.workspace.members.dify_config.CONSOLE_WEB_URL", "https://console.example.com"), patch("controllers.console.workspace.members._count_new_member_invites", return_value=(1, 1)), - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY), ): with app.test_request_context( "/workspaces/current/members/invite-email", @@ -90,26 +95,25 @@ class TestMemberInviteEmailApi: mock_invite_member, mock_get_features, app, + config_overrides, ): """When RBAC is enabled, any non-empty role string should be accepted.""" + config_overrides(RBAC_ENABLED=True) mock_get_features.return_value = _build_feature_flags() mock_invite_member.return_value = "rbac-token" tenant = SimpleNamespace(id="tenant-1", name="Test Tenant") - with patch("controllers.console.workspace.members.dify_config") as mock_config: - mock_config.RBAC_ENABLED = True - mock_config.CONSOLE_WEB_URL = "https://console.example.com" - with app.test_request_context( - "/workspaces/current/members/invite-email", - method="POST", - json={"emails": ["user@example.com"], "role": "rbac-role-id-abc", "language": "en-US"}, - ): - account = Account(name="tester", email="tester@example.com") - account._current_tenant = tenant - g._login_user = account - g._current_tenant = tenant - response, status_code = MemberInviteEmailApi().post() + with app.test_request_context( + "/workspaces/current/members/invite-email", + method="POST", + json={"emails": ["user@example.com"], "role": "rbac-role-id-abc", "language": "en-US"}, + ): + account = Account(name="tester", email="tester@example.com") + account._current_tenant = tenant + g._login_user = account + g._current_tenant = tenant + response, status_code = MemberInviteEmailApi().post() assert status_code == 201 mock_invite_member.assert_called_once() @@ -131,20 +135,17 @@ class TestMemberInviteEmailApi: tenant = SimpleNamespace(id="tenant-1", name="Test Tenant") - with patch("controllers.console.workspace.members.dify_config") as mock_config: - mock_config.RBAC_ENABLED = False - mock_config.CONSOLE_WEB_URL = "https://console.example.com" - with app.test_request_context( - "/workspaces/current/members/invite-email", - method="POST", - json={"emails": ["user@example.com"], "role": "invalid-role", "language": "en-US"}, - ): - account = Account(name="tester", email="tester@example.com") - account._current_tenant = tenant - g._login_user = account - g._current_tenant = tenant - with pytest.raises(InvalidMemberRoleError) as exc_info: - MemberInviteEmailApi().post() + with app.test_request_context( + "/workspaces/current/members/invite-email", + method="POST", + json={"emails": ["user@example.com"], "role": "invalid-role", "language": "en-US"}, + ): + account = Account(name="tester", email="tester@example.com") + account._current_tenant = tenant + g._login_user = account + g._current_tenant = tenant + with pytest.raises(InvalidMemberRoleError) as exc_info: + MemberInviteEmailApi().post() assert exc_info.value.error_code == "invalid_role" assert exc_info.value.data == {"code": "invalid_role", "message": "Invalid role.", "status": 400} @@ -164,19 +165,16 @@ class TestMemberInviteEmailApi: tenant = SimpleNamespace(id="tenant-1", name="Test Tenant") - with patch("controllers.console.workspace.members.dify_config") as mock_config: - mock_config.RBAC_ENABLED = False - mock_config.CONSOLE_WEB_URL = "https://console.example.com" - with app.test_request_context( - "/workspaces/current/members/invite-email", - method="POST", - json={"emails": ["user@example.com"], "role": "owner", "language": "en-US"}, - ): - account = Account(name="tester", email="tester@example.com") - account._current_tenant = tenant - g._login_user = account - g._current_tenant = tenant - with pytest.raises(InvalidMemberRoleError) as exc_info: - MemberInviteEmailApi().post() + with app.test_request_context( + "/workspaces/current/members/invite-email", + method="POST", + json={"emails": ["user@example.com"], "role": "owner", "language": "en-US"}, + ): + account = Account(name="tester", email="tester@example.com") + account._current_tenant = tenant + g._login_user = account + g._current_tenant = tenant + with pytest.raises(InvalidMemberRoleError) as exc_info: + MemberInviteEmailApi().post() assert exc_info.value.error_code == "invalid_role" diff --git a/api/tests/unit_tests/controllers/console/workspace/test_members.py b/api/tests/unit_tests/controllers/console/workspace/test_members.py index e7b60184275..5d9148dee5a 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_members.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_members.py @@ -123,6 +123,13 @@ class TestMemberListApi: class TestMemberInviteEmailApi: + @pytest.fixture(autouse=True) + def _invite_config(self, config_overrides) -> None: + config_overrides( + CONSOLE_WEB_URL="http://x", + DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY, + ) + @pytest.fixture(autouse=True) def _mock_member_invite_lock(self): with patch("controllers.console.workspace.members.redis_client.lock", return_value=nullcontext()): @@ -151,8 +158,6 @@ class TestMemberInviteEmailApi: patch( "controllers.console.workspace.members.RegisterService.invite_new_member", return_value="token" ) as mock_invite, - patch("controllers.console.workspace.members.dify_config.CONSOLE_WEB_URL", "http://x"), - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY), ): result, status = method(api, user) @@ -163,7 +168,8 @@ class TestMemberInviteEmailApi: mock_invite.assert_called_once() assert mock_invite.call_args.kwargs["email"] == "a@test.com" - def test_invite_limit_exceeded(self, app: Flask): + def test_invite_limit_exceeded(self, app: Flask, config_overrides): + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) api = MemberInviteEmailApi() method = unwrap(api.post) @@ -182,12 +188,12 @@ class TestMemberInviteEmailApi: app.test_request_context("/", json=payload), patch("controllers.console.workspace.members.FeatureService.get_features", return_value=features), patch("controllers.console.workspace.members._count_new_member_invites", return_value=(1, 1)), - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.ENTERPRISE), ): with pytest.raises(WorkspaceMembersLimitExceeded): method(api, user) - def test_invite_cloud_member_limit_exceeded(self, app: Flask): + def test_invite_cloud_member_limit_exceeded(self, app: Flask, config_overrides): + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD) api = MemberInviteEmailApi() method = unwrap(api.post) @@ -208,7 +214,6 @@ class TestMemberInviteEmailApi: patch("controllers.console.workspace.members.FeatureService.get_features", return_value=features), patch("controllers.console.workspace.members._count_new_member_invites", return_value=(2, 2)), patch("controllers.console.workspace.members._count_current_members", return_value=9), - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.CLOUD), ): with pytest.raises(WorkspaceMembersLimitExceeded): method(api, user) @@ -236,8 +241,6 @@ class TestMemberInviteEmailApi: "controllers.console.workspace.members.RegisterService.invite_new_member", side_effect=AccountAlreadyInTenantError(), ), - patch("controllers.console.workspace.members.dify_config.CONSOLE_WEB_URL", "http://x"), - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY), ): result, status = method(api, user) @@ -302,14 +305,13 @@ class TestMemberInviteEmailApi: "controllers.console.workspace.members.RegisterService.invite_new_member", side_effect=Exception("boom"), ), - patch("controllers.console.workspace.members.dify_config.CONSOLE_WEB_URL", "http://x"), - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY), ): result, _ = method(api, user) assert result["invitation_results"][0]["status"] == "failed" - def test_invite_seats_limit_exceeded(self, app: Flask): + def test_invite_seats_limit_exceeded(self, app: Flask, config_overrides): + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) api = MemberInviteEmailApi() method = unwrap(api.post) @@ -334,7 +336,6 @@ class TestMemberInviteEmailApi: return_value=license_info, ) as mock_get_license, patch("controllers.console.workspace.members.RegisterService.invite_new_member") as mock_invite, - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.ENTERPRISE), ): with pytest.raises(SeatsLimitExceeded): method(api, user) @@ -343,7 +344,8 @@ class TestMemberInviteEmailApi: license_info.seats.is_available.assert_called_once_with(2) mock_invite.assert_not_called() - def test_invite_existing_accounts_do_not_consume_seats(self, app: Flask): + def test_invite_existing_accounts_do_not_consume_seats(self, app: Flask, config_overrides): + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) api = MemberInviteEmailApi() method = unwrap(api.post) @@ -370,8 +372,6 @@ class TestMemberInviteEmailApi: patch( "controllers.console.workspace.members.RegisterService.invite_new_member", return_value="token" ) as mock_invite, - patch("controllers.console.workspace.members.dify_config.CONSOLE_WEB_URL", "http://x"), - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.ENTERPRISE), ): result, status = method(api, user) @@ -381,7 +381,8 @@ class TestMemberInviteEmailApi: license_info.seats.is_available.assert_not_called() assert mock_invite.call_count == 2 - def test_invite_mixed_accounts_with_available_seats(self, app: Flask): + def test_invite_mixed_accounts_with_available_seats(self, app: Flask, config_overrides): + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) api = MemberInviteEmailApi() method = unwrap(api.post) @@ -408,8 +409,6 @@ class TestMemberInviteEmailApi: patch( "controllers.console.workspace.members.RegisterService.invite_new_member", return_value="token" ) as mock_invite, - patch("controllers.console.workspace.members.dify_config.CONSOLE_WEB_URL", "http://x"), - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.ENTERPRISE), ): result, status = method(api, user) @@ -444,8 +443,6 @@ class TestMemberInviteEmailApi: return_value=license_info, ) as mock_get_license, patch("controllers.console.workspace.members.RegisterService.invite_new_member", return_value="token"), - patch("controllers.console.workspace.members.dify_config.CONSOLE_WEB_URL", "http://x"), - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.COMMUNITY), ): result, status = method(api, user) @@ -454,7 +451,8 @@ class TestMemberInviteEmailApi: mock_get_license.assert_not_called() license_info.seats.is_available.assert_not_called() - def test_invite_seats_error_is_reported_as_failed_result(self, app: Flask): + def test_invite_seats_error_is_reported_as_failed_result(self, app: Flask, config_overrides): + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) api = MemberInviteEmailApi() method = unwrap(api.post) @@ -482,8 +480,6 @@ class TestMemberInviteEmailApi: "controllers.console.workspace.members.RegisterService.invite_new_member", side_effect=SeatsLimitExceededError("licensed seats limit exceeded"), ), - patch("controllers.console.workspace.members.dify_config.CONSOLE_WEB_URL", "http://x"), - patch("controllers.console.workspace.members.dify_config.DEPLOYMENT_EDITION", DeploymentEdition.ENTERPRISE), ): result, status = method(api, user) diff --git a/api/tests/unit_tests/controllers/console/workspace/test_rbac.py b/api/tests/unit_tests/controllers/console/workspace/test_rbac.py index ac8975f2d2d..d9a88e630bd 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_rbac.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_rbac.py @@ -37,9 +37,13 @@ def app(): return flask_app -def _enabled(enabled: bool): - deployment_edition = DeploymentEdition.ENTERPRISE if enabled else DeploymentEdition.COMMUNITY - return patch("controllers.console.workspace.rbac.dify_config.DEPLOYMENT_EDITION", deployment_edition) +@pytest.fixture(autouse=True) +def _rbac_config(config_overrides) -> None: + config_overrides( + DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE, + RBAC_ENABLED=True, + LOGIN_DISABLED=True, + ) def _account() -> Account: @@ -198,10 +202,10 @@ class TestPydanticModels: class TestPaginationMapping: - def test_roles_get_returns_legacy_compatible_roles_when_rbac_disabled(self, app): + def test_roles_get_returns_legacy_compatible_roles_when_rbac_disabled(self, app, config_overrides): + config_overrides(RBAC_ENABLED=False) with ( app.test_request_context("/workspaces/current/rbac/roles?page=1&limit=2&include_owner=1"), - patch("controllers.console.workspace.rbac.dify_config.RBAC_ENABLED", False), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list") as mock_list, ): @@ -256,10 +260,10 @@ class TestPaginationMapping: } mock_list.assert_not_called() - def test_roles_get_filters_out_owner_when_include_owner_is_zero(self, app): + def test_roles_get_filters_out_owner_when_include_owner_is_zero(self, app, config_overrides): + config_overrides(RBAC_ENABLED=False) with ( app.test_request_context("/workspaces/current/rbac/roles?include_owner=0"), - patch("controllers.console.workspace.rbac.dify_config.RBAC_ENABLED", False), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list"), ): @@ -268,10 +272,10 @@ class TestPaginationMapping: names = [r["name"] for r in response["data"]] assert "owner" not in names - def test_roles_get_keeps_owner_when_include_owner_is_one(self, app): + def test_roles_get_keeps_owner_when_include_owner_is_one(self, app, config_overrides): + config_overrides(RBAC_ENABLED=False) with ( app.test_request_context("/workspaces/current/rbac/roles?include_owner=1"), - patch("controllers.console.workspace.rbac.dify_config.RBAC_ENABLED", False), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list"), ): @@ -283,10 +287,10 @@ class TestPaginationMapping: names = [r["name"] for r in response["data"]] assert "owner" in names - def test_roles_get_filters_out_owner_by_default(self, app): + def test_roles_get_filters_out_owner_by_default(self, app, config_overrides): + config_overrides(RBAC_ENABLED=False) with ( app.test_request_context("/workspaces/current/rbac/roles"), - patch("controllers.console.workspace.rbac.dify_config.RBAC_ENABLED", False), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list"), ): @@ -298,7 +302,6 @@ class TestPaginationMapping: def test_roles_get_forwards_outer_pagination_params(self, app): with ( app.test_request_context("/workspaces/current/rbac/roles?page=2&limit=50&reverse=true&include_owner=1"), - patch("controllers.console.workspace.rbac.dify_config.RBAC_ENABLED", True), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.Roles.list") as mock_list, patch("controllers.console.workspace.rbac._dump", return_value={}), @@ -324,7 +327,6 @@ class TestResourceAccessScopeBindings: json={"scope": "all"}, ), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-actor")), - patch("controllers.console.workspace.rbac.dify_config.RBAC_ENABLED", True), patch( "controllers.console.workspace.rbac.svc.RBACService.AppAccess.replace_whitelist", return_value=rbac_mod.svc.ResourceWhitelist(), @@ -345,7 +347,6 @@ class TestResourceAccessScopeBindings: json={"scope": "all"}, ), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-actor")), - patch("controllers.console.workspace.rbac.dify_config.RBAC_ENABLED", True), patch( "controllers.console.workspace.rbac.svc.RBACService.DatasetAccess.replace_whitelist", return_value=rbac_mod.svc.ResourceWhitelist(), @@ -364,7 +365,6 @@ class TestResourceAccessScopeBindings: json={"scope": "specific"}, ), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-actor")), - patch("controllers.console.workspace.rbac.dify_config.RBAC_ENABLED", True), patch( "controllers.console.workspace.rbac.svc.RBACService.DatasetAccess.replace_whitelist", return_value=rbac_mod.svc.ResourceWhitelist(), @@ -454,7 +454,6 @@ class TestPaginationForwarding: app.test_request_context( "/workspaces/current/rbac/access-policies?resource_type=app&page=3&limit=25&reverse=false" ), - _enabled(True), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.AccessPolicies.list") as mock_list, patch("controllers.console.workspace.rbac._dump", return_value={}), @@ -471,7 +470,6 @@ class TestPaginationForwarding: def test_workspace_app_matrix_forwards_outer_pagination_params(self, app): with ( app.test_request_context("/workspaces/current/rbac/workspace/apps/access-policy?page=4&limit=10"), - _enabled(True), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.WorkspaceAccess.app_matrix") as mock_list, patch("controllers.console.workspace.rbac._dump", return_value={}), @@ -489,7 +487,6 @@ class TestPaginationForwarding: app.test_request_context( "/workspaces/current/rbac/workspace/datasets/access-policy?page=5&limit=15&reverse=true" ), - _enabled(True), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.WorkspaceAccess.dataset_matrix") as mock_list, patch("controllers.console.workspace.rbac._dump", return_value={}), @@ -507,7 +504,6 @@ class TestAccessPolicyBindingLockUnlock: def test_lock_forwards_binding_id(self, app): with ( app.test_request_context("/workspaces/current/rbac/access-policy-bindings/binding-1/lock", method="PUT"), - _enabled(True), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.AccessPolicyBindings.lock") as mock_lock, patch("controllers.console.workspace.rbac._dump", return_value={}), @@ -521,7 +517,6 @@ class TestAccessPolicyBindingLockUnlock: def test_unlock_forwards_binding_id(self, app): with ( app.test_request_context("/workspaces/current/rbac/access-policy-bindings/binding-1/unlock", method="PUT"), - _enabled(True), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.AccessPolicyBindings.unlock") as mock_unlock, patch("controllers.console.workspace.rbac._dump", return_value={}), @@ -537,7 +532,6 @@ class TestRoleCopy: def test_role_copy_forwards_path_id(self, app): with ( app.test_request_context("/workspaces/current/rbac/roles/role-1/copy", method="POST", json={}), - _enabled(True), patch("controllers.console.workspace.rbac._current_ids", return_value=("tenant-1", "acct-1")), patch("controllers.console.workspace.rbac.svc.RBACService.Roles.copy") as mock_copy, patch("controllers.console.workspace.rbac._dump", return_value={}), @@ -555,8 +549,6 @@ class TestWorkspaceRbacGuards: method="POST", json={"name": "test_role", "permission_keys": []}, ), - patch("libs.login.dify_config.LOGIN_DISABLED", True), - patch("controllers.console.wraps.dify_config.RBAC_ENABLED", True), patch( "controllers.common.wraps.current_account_with_tenant", return_value=(_account(), "tenant-1"), @@ -576,8 +568,6 @@ class TestWorkspaceRbacGuards: method="POST", json={"name": "full_access", "resource_type": "app", "permission_keys": []}, ), - patch("libs.login.dify_config.LOGIN_DISABLED", True), - patch("controllers.console.wraps.dify_config.RBAC_ENABLED", True), patch( "controllers.common.wraps.current_account_with_tenant", return_value=(_account(), "tenant-1"), diff --git a/api/tests/unit_tests/core/repositories/test_factory.py b/api/tests/unit_tests/core/repositories/test_factory.py index 700a986a310..a98bdb3f93f 100644 --- a/api/tests/unit_tests/core/repositories/test_factory.py +++ b/api/tests/unit_tests/core/repositories/test_factory.py @@ -37,6 +37,13 @@ def sqlite_session_factory(sqlite_engine: Engine) -> sessionmaker[Session]: class TestRepositoryFactory: """Test cases for RepositoryFactory.""" + @pytest.fixture(autouse=True) + def _repository_config(self, config_overrides) -> None: + config_overrides( + CORE_WORKFLOW_EXECUTION_REPOSITORY="unittest.mock.MagicMock", + CORE_WORKFLOW_NODE_EXECUTION_REPOSITORY="unittest.mock.MagicMock", + ) + def test_import_string_success(self): """Test successful class import.""" # Test importing a real class @@ -62,12 +69,8 @@ class TestRepositoryFactory: import_string("invalidpath") assert "doesn't look like a module path" in str(exc_info.value) - @patch("core.repositories.factory.dify_config") - def test_create_workflow_execution_repository_success(self, mock_config, sqlite_session_factory): + def test_create_workflow_execution_repository_success(self, sqlite_session_factory): """Test successful WorkflowExecutionRepository creation.""" - # Setup mock configuration - mock_config.CORE_WORKFLOW_EXECUTION_REPOSITORY = "unittest.mock.MagicMock" - # Create non-database dependencies mock_user = Account(name="Test Account", email="test@example.com") app_id = "test-app-id" @@ -98,11 +101,9 @@ class TestRepositoryFactory: ) assert result is mock_repository_instance - @patch("core.repositories.factory.dify_config") - def test_create_workflow_execution_repository_import_error(self, mock_config, sqlite_session_factory): + def test_create_workflow_execution_repository_import_error(self, sqlite_session_factory, config_overrides): """Test WorkflowExecutionRepository creation with import error.""" - # Setup mock configuration with invalid class path - mock_config.CORE_WORKFLOW_EXECUTION_REPOSITORY = "invalid.module.InvalidClass" + config_overrides(CORE_WORKFLOW_EXECUTION_REPOSITORY="invalid.module.InvalidClass") mock_user = Account(name="Test Account", email="test@example.com") @@ -116,12 +117,8 @@ class TestRepositoryFactory: ) assert "Failed to create WorkflowExecutionRepository" in str(exc_info.value) - @patch("core.repositories.factory.dify_config") - def test_create_workflow_execution_repository_instantiation_error(self, mock_config, sqlite_session_factory): + def test_create_workflow_execution_repository_instantiation_error(self, sqlite_session_factory): """Test WorkflowExecutionRepository creation with instantiation error.""" - # Setup mock configuration - mock_config.CORE_WORKFLOW_EXECUTION_REPOSITORY = "unittest.mock.MagicMock" - mock_user = Account(name="Test Account", email="test@example.com") # Create a mock repository class that raises exception on instantiation @@ -140,12 +137,8 @@ class TestRepositoryFactory: ) assert "Failed to create WorkflowExecutionRepository" in str(exc_info.value) - @patch("core.repositories.factory.dify_config") - def test_create_workflow_node_execution_repository_success(self, mock_config, sqlite_session_factory): + def test_create_workflow_node_execution_repository_success(self, sqlite_session_factory): """Test successful WorkflowNodeExecutionRepository creation.""" - # Setup mock configuration - mock_config.CORE_WORKFLOW_NODE_EXECUTION_REPOSITORY = "unittest.mock.MagicMock" - # Create non-database dependencies mock_user = EndUser() app_id = "test-app-id" @@ -176,11 +169,9 @@ class TestRepositoryFactory: ) assert result is mock_repository_instance - @patch("core.repositories.factory.dify_config") - def test_create_workflow_node_execution_repository_import_error(self, mock_config, sqlite_session_factory): + def test_create_workflow_node_execution_repository_import_error(self, sqlite_session_factory, config_overrides): """Test WorkflowNodeExecutionRepository creation with import error.""" - # Setup mock configuration with invalid class path - mock_config.CORE_WORKFLOW_NODE_EXECUTION_REPOSITORY = "invalid.module.InvalidClass" + config_overrides(CORE_WORKFLOW_NODE_EXECUTION_REPOSITORY="invalid.module.InvalidClass") mock_user = EndUser() @@ -194,12 +185,8 @@ class TestRepositoryFactory: ) assert "Failed to create WorkflowNodeExecutionRepository" in str(exc_info.value) - @patch("core.repositories.factory.dify_config") - def test_create_workflow_node_execution_repository_instantiation_error(self, mock_config, sqlite_session_factory): + def test_create_workflow_node_execution_repository_instantiation_error(self, sqlite_session_factory): """Test WorkflowNodeExecutionRepository creation with instantiation error.""" - # Setup mock configuration - mock_config.CORE_WORKFLOW_NODE_EXECUTION_REPOSITORY = "unittest.mock.MagicMock" - mock_user = EndUser() # Create a mock repository class that raises exception on instantiation @@ -224,12 +211,8 @@ class TestRepositoryFactory: error = RepositoryImportError(error_message) assert str(error) == error_message - @patch("core.repositories.factory.dify_config") - def test_create_with_engine_instead_of_sessionmaker(self, mock_config, sqlite_engine: Engine): + def test_create_with_engine_instead_of_sessionmaker(self, sqlite_engine: Engine): """Test repository creation with Engine instead of sessionmaker.""" - # Setup mock configuration - mock_config.CORE_WORKFLOW_EXECUTION_REPOSITORY = "unittest.mock.MagicMock" - # Pass the real Engine directly instead of wrapping it in sessionmaker mock_user = Account(name="Test Account", email="test@example.com") app_id = "test-app-id" diff --git a/api/tests/unit_tests/services/enterprise/test_account_deletion_sync.py b/api/tests/unit_tests/services/enterprise/test_account_deletion_sync.py index fd631dc91ec..c089dab3628 100644 --- a/api/tests/unit_tests/services/enterprise/test_account_deletion_sync.py +++ b/api/tests/unit_tests/services/enterprise/test_account_deletion_sync.py @@ -22,6 +22,11 @@ from services.enterprise.account_deletion_sync import ( ) +@pytest.fixture(autouse=True) +def _enterprise_edition(config_overrides) -> None: + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) + + class TestQueueTask: def test_queue_task_redis_error(self, caplog: pytest.LogCaptureFixture): with patch("services.enterprise.account_deletion_sync.redis_client") as mock_redis: @@ -53,36 +58,24 @@ class TestSyncWorkspaceMemberRemoval: workspace_id = str(uuid4()) member_id = str(uuid4()) - with patch("services.enterprise.account_deletion_sync.dify_config") as mock_config: - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE + result = sync_workspace_member_removal(workspace_id=workspace_id, member_id=member_id, source="removed") - result = sync_workspace_member_removal(workspace_id=workspace_id, member_id=member_id, source="removed") + assert result is True + mock_queue_task.assert_called_once_with(workspace_id=workspace_id, member_id=member_id, source="removed") - assert result is True - mock_queue_task.assert_called_once_with(workspace_id=workspace_id, member_id=member_id, source="removed") + def test_sync_workspace_member_removal_non_enterprise_edition(self, mock_queue_task, config_overrides): + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY) + result = sync_workspace_member_removal(workspace_id=str(uuid4()), member_id=str(uuid4()), source="test_source") - def test_sync_workspace_member_removal_non_enterprise_edition(self, mock_queue_task): - with patch("services.enterprise.account_deletion_sync.dify_config") as mock_config: - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY - - result = sync_workspace_member_removal( - workspace_id=str(uuid4()), member_id=str(uuid4()), source="test_source" - ) - - assert result is True - mock_queue_task.assert_not_called() + assert result is True + mock_queue_task.assert_not_called() def test_sync_workspace_member_removal_queue_failure(self, mock_queue_task): mock_queue_task.return_value = False - with patch("services.enterprise.account_deletion_sync.dify_config") as mock_config: - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE + result = sync_workspace_member_removal(workspace_id=str(uuid4()), member_id=str(uuid4()), source="test_source") - result = sync_workspace_member_removal( - workspace_id=str(uuid4()), member_id=str(uuid4()), source="test_source" - ) - - assert result is False + assert result is False @pytest.mark.parametrize("sqlite_session", [(TenantAccountJoin,)], indirect=True) @@ -93,14 +86,15 @@ class TestSyncAccountDeletion: mock_queue.return_value = True yield mock_queue - def test_sync_account_deletion_non_enterprise_edition(self, mock_queue_task, sqlite_session: Session) -> None: - with patch("services.enterprise.account_deletion_sync.dify_config") as mock_config: - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY + def test_sync_account_deletion_non_enterprise_edition( + self, mock_queue_task, sqlite_session: Session, config_overrides + ) -> None: + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY) - result = sync_account_deletion(account_id=str(uuid4()), source="account_deleted", session=sqlite_session) + result = sync_account_deletion(account_id=str(uuid4()), source="account_deleted", session=sqlite_session) - assert result is True - mock_queue_task.assert_not_called() + assert result is True + mock_queue_task.assert_not_called() def test_sync_account_deletion_multiple_workspaces(self, sqlite_session: Session, mock_queue_task) -> None: account_id = str(uuid4()) @@ -111,25 +105,19 @@ class TestSyncAccountDeletion: sqlite_session.add(join) sqlite_session.commit() - with patch("services.enterprise.account_deletion_sync.dify_config") as mock_config: - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE + result = sync_account_deletion(account_id=account_id, source="account_deleted", session=sqlite_session) - result = sync_account_deletion(account_id=account_id, source="account_deleted", session=sqlite_session) + assert result is True + assert mock_queue_task.call_count == 3 - assert result is True - assert mock_queue_task.call_count == 3 - - queued_workspace_ids = {call.kwargs["workspace_id"] for call in mock_queue_task.call_args_list} - assert queued_workspace_ids == set(tenant_ids) + queued_workspace_ids = {call.kwargs["workspace_id"] for call in mock_queue_task.call_args_list} + assert queued_workspace_ids == set(tenant_ids) def test_sync_account_deletion_no_workspaces(self, sqlite_session: Session, mock_queue_task) -> None: - with patch("services.enterprise.account_deletion_sync.dify_config") as mock_config: - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE + result = sync_account_deletion(account_id=str(uuid4()), source="account_deleted", session=sqlite_session) - result = sync_account_deletion(account_id=str(uuid4()), source="account_deleted", session=sqlite_session) - - assert result is True - mock_queue_task.assert_not_called() + assert result is True + mock_queue_task.assert_not_called() def test_sync_account_deletion_partial_failure(self, sqlite_session: Session, mock_queue_task) -> None: account_id = str(uuid4()) @@ -146,13 +134,10 @@ class TestSyncAccountDeletion: mock_queue_task.side_effect = queue_side_effect - with patch("services.enterprise.account_deletion_sync.dify_config") as mock_config: - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE + result = sync_account_deletion(account_id=account_id, source="account_deleted", session=sqlite_session) - result = sync_account_deletion(account_id=account_id, source="account_deleted", session=sqlite_session) - - assert result is False - assert mock_queue_task.call_count == 3 + assert result is False + assert mock_queue_task.call_count == 3 def test_sync_account_deletion_all_failures(self, sqlite_session: Session, mock_queue_task) -> None: account_id = str(uuid4()) @@ -164,10 +149,7 @@ class TestSyncAccountDeletion: mock_queue_task.return_value = False - with patch("services.enterprise.account_deletion_sync.dify_config") as mock_config: - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE + result = sync_account_deletion(account_id=account_id, source="account_deleted", session=sqlite_session) - result = sync_account_deletion(account_id=account_id, source="account_deleted", session=sqlite_session) - - assert result is False - mock_queue_task.assert_called_once() + assert result is False + mock_queue_task.assert_called_once() diff --git a/api/tests/unit_tests/services/enterprise/test_enterprise_service.py b/api/tests/unit_tests/services/enterprise/test_enterprise_service.py index 7ec5d1c01e6..d50f276bc75 100644 --- a/api/tests/unit_tests/services/enterprise/test_enterprise_service.py +++ b/api/tests/unit_tests/services/enterprise/test_enterprise_service.py @@ -269,13 +269,13 @@ class TestJoinDefaultWorkspace: class TestTryJoinDefaultWorkspace: - def test_try_join_default_workspace_non_enterprise_edition_noop(self): - with ( - patch("services.enterprise.enterprise_service.dify_config") as mock_config, - patch("services.enterprise.enterprise_service.EnterpriseService.join_default_workspace") as mock_join, - ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY + @pytest.fixture(autouse=True) + def _enterprise_edition(self, config_overrides) -> None: + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) + def test_try_join_default_workspace_non_enterprise_edition_noop(self, config_overrides): + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY) + with patch("services.enterprise.enterprise_service.EnterpriseService.join_default_workspace") as mock_join: try_join_default_workspace("11111111-1111-1111-1111-111111111111") mock_join.assert_not_called() @@ -283,11 +283,7 @@ class TestTryJoinDefaultWorkspace: def test_try_join_default_workspace_successful_join_does_not_raise(self): account_id = "11111111-1111-1111-1111-111111111111" - with ( - patch("services.enterprise.enterprise_service.dify_config") as mock_config, - patch("services.enterprise.enterprise_service.EnterpriseService.join_default_workspace") as mock_join, - ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE + with patch("services.enterprise.enterprise_service.EnterpriseService.join_default_workspace") as mock_join: mock_join.return_value = DefaultWorkspaceJoinResult( workspace_id="22222222-2222-2222-2222-222222222222", joined=True, @@ -302,11 +298,7 @@ class TestTryJoinDefaultWorkspace: def test_try_join_default_workspace_skipped_join_does_not_raise(self): account_id = "11111111-1111-1111-1111-111111111111" - with ( - patch("services.enterprise.enterprise_service.dify_config") as mock_config, - patch("services.enterprise.enterprise_service.EnterpriseService.join_default_workspace") as mock_join, - ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE + with patch("services.enterprise.enterprise_service.EnterpriseService.join_default_workspace") as mock_join: mock_join.return_value = DefaultWorkspaceJoinResult( workspace_id="", joined=False, @@ -321,11 +313,7 @@ class TestTryJoinDefaultWorkspace: def test_try_join_default_workspace_api_failure_soft_fails(self): account_id = "11111111-1111-1111-1111-111111111111" - with ( - patch("services.enterprise.enterprise_service.dify_config") as mock_config, - patch("services.enterprise.enterprise_service.EnterpriseService.join_default_workspace") as mock_join, - ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE + with patch("services.enterprise.enterprise_service.EnterpriseService.join_default_workspace") as mock_join: mock_join.side_effect = Exception("network failure") # Should not raise @@ -334,11 +322,8 @@ class TestTryJoinDefaultWorkspace: mock_join.assert_called_once_with(account_id=account_id) def test_try_join_default_workspace_invalid_account_id_soft_fails(self): - with patch("services.enterprise.enterprise_service.dify_config") as mock_config: - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE - - # Should not raise even though UUID parsing fails inside join_default_workspace - try_join_default_workspace("not-a-uuid") + # Should not raise even though UUID parsing fails inside join_default_workspace + try_join_default_workspace("not-a-uuid") # --------------------------------------------------------------------------- @@ -351,19 +336,19 @@ _EE_SVC = "services.enterprise.enterprise_service" class TestGetCachedLicenseStatus: """Tests for EnterpriseService.get_cached_license_status.""" - def test_returns_none_outside_enterprise_edition(self): - with patch(f"{_EE_SVC}.dify_config") as mock_config: - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY + @pytest.fixture(autouse=True) + def _enterprise_edition(self, config_overrides) -> None: + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) - assert EnterpriseService.get_cached_license_status() is None + def test_returns_none_outside_enterprise_edition(self, config_overrides): + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY) + assert EnterpriseService.get_cached_license_status() is None def test_cache_hit_returns_license_status_enum(self): with ( - patch(f"{_EE_SVC}.dify_config") as mock_config, patch(f"{_EE_SVC}.redis_client") as mock_redis, patch.object(EnterpriseService, "get_info") as mock_get_info, ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE mock_redis.get.return_value = b"active" result = EnterpriseService.get_cached_license_status() @@ -374,11 +359,9 @@ class TestGetCachedLicenseStatus: def test_cache_miss_fetches_api_and_caches_valid_status(self): with ( - patch(f"{_EE_SVC}.dify_config") as mock_config, patch(f"{_EE_SVC}.redis_client") as mock_redis, patch.object(EnterpriseService, "get_info") as mock_get_info, ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE mock_redis.get.return_value = None mock_get_info.return_value = {"License": {"status": "active"}} @@ -391,11 +374,9 @@ class TestGetCachedLicenseStatus: def test_cache_miss_fetches_api_and_caches_invalid_status_with_short_ttl(self): with ( - patch(f"{_EE_SVC}.dify_config") as mock_config, patch(f"{_EE_SVC}.redis_client") as mock_redis, patch.object(EnterpriseService, "get_info") as mock_get_info, ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE mock_redis.get.return_value = None mock_get_info.return_value = {"License": {"status": "expired"}} @@ -408,11 +389,9 @@ class TestGetCachedLicenseStatus: def test_redis_read_failure_falls_through_to_api(self): with ( - patch(f"{_EE_SVC}.dify_config") as mock_config, patch(f"{_EE_SVC}.redis_client") as mock_redis, patch.object(EnterpriseService, "get_info") as mock_get_info, ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE mock_redis.get.side_effect = ConnectionError("redis down") mock_get_info.return_value = {"License": {"status": "active"}} @@ -423,11 +402,9 @@ class TestGetCachedLicenseStatus: def test_redis_write_failure_still_returns_status(self): with ( - patch(f"{_EE_SVC}.dify_config") as mock_config, patch(f"{_EE_SVC}.redis_client") as mock_redis, patch.object(EnterpriseService, "get_info") as mock_get_info, ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE mock_redis.get.return_value = None mock_redis.setex.side_effect = ConnectionError("redis down") mock_get_info.return_value = {"License": {"status": "expiring"}} @@ -438,11 +415,9 @@ class TestGetCachedLicenseStatus: def test_api_failure_returns_none(self): with ( - patch(f"{_EE_SVC}.dify_config") as mock_config, patch(f"{_EE_SVC}.redis_client") as mock_redis, patch.object(EnterpriseService, "get_info") as mock_get_info, ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE mock_redis.get.return_value = None mock_get_info.side_effect = Exception("network failure") @@ -450,11 +425,9 @@ class TestGetCachedLicenseStatus: def test_api_returns_no_license_info(self): with ( - patch(f"{_EE_SVC}.dify_config") as mock_config, patch(f"{_EE_SVC}.redis_client") as mock_redis, patch.object(EnterpriseService, "get_info") as mock_get_info, ): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.ENTERPRISE mock_redis.get.return_value = None mock_get_info.return_value = {} # no "License" key diff --git a/api/tests/unit_tests/services/enterprise/test_rbac_service.py b/api/tests/unit_tests/services/enterprise/test_rbac_service.py index ca9f7d1764d..5d5b6b1273a 100644 --- a/api/tests/unit_tests/services/enterprise/test_rbac_service.py +++ b/api/tests/unit_tests/services/enterprise/test_rbac_service.py @@ -536,6 +536,10 @@ class TestWorkspaceAccess: @pytest.mark.parametrize("sqlite_session", [(TenantAccountJoin,)], indirect=True) class TestMyPermissions: + @pytest.fixture(autouse=True) + def _rbac_enabled(self, config_overrides) -> None: + config_overrides(RBAC_ENABLED=True) + def test_resource_snapshot_maps_defaults_and_overrides(self, sqlite_session: Session): snapshot = svc.ResourcePermissionSnapshot( default_permission_keys=["app.acl.view_layout"], @@ -559,8 +563,7 @@ class TestMyPermissions: "dataset": {"default_permission_keys": [], "overrides": []}, } - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", True): - out = svc.RBACService.MyPermissions.get("tenant-1", "acct-1", session=sqlite_session) + out = svc.RBACService.MyPermissions.get("tenant-1", "acct-1", session=sqlite_session) call = _call_args(mock_send) assert call.method == "GET" @@ -612,13 +615,14 @@ class TestMyPermissions: app_keys: list[str], dataset_keys: list[str], sqlite_session: Session, + config_overrides, ): + config_overrides(RBAC_ENABLED=False) sqlite_session.add( TenantAccountJoin(tenant_id="tenant-1", account_id="acct-1", role=svc.TenantAccountRole(role)) ) sqlite_session.commit() - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", False): - out = svc.RBACService.MyPermissions.get("tenant-1", "acct-1", session=sqlite_session) + out = svc.RBACService.MyPermissions.get("tenant-1", "acct-1", session=sqlite_session) mock_send.assert_not_called() assert out.workspace.permission_keys == workspace_keys @@ -654,13 +658,14 @@ class TestMyPermissions: role: str, expected_snippet_keys: set[str], sqlite_session: Session, + config_overrides, ): + config_overrides(RBAC_ENABLED=False) sqlite_session.add( TenantAccountJoin(tenant_id="tenant-1", account_id="acct-1", role=svc.TenantAccountRole(role)) ) sqlite_session.commit() - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", False): - out = svc.RBACService.MyPermissions.get("tenant-1", "acct-1", session=sqlite_session) + out = svc.RBACService.MyPermissions.get("tenant-1", "acct-1", session=sqlite_session) actual_snippet_keys = { permission_key for permission_key in out.workspace.permission_keys if permission_key.startswith("snippets.") @@ -669,9 +674,11 @@ class TestMyPermissions: mock_send.assert_not_called() assert actual_snippet_keys == expected_snippet_keys - def test_get_returns_empty_when_role_missing_and_rbac_disabled(self, mock_send: MagicMock, sqlite_session: Session): - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", False): - out = svc.RBACService.MyPermissions.get("tenant-1", "acct-1", session=sqlite_session) + def test_get_returns_empty_when_role_missing_and_rbac_disabled( + self, mock_send: MagicMock, sqlite_session: Session, config_overrides + ): + config_overrides(RBAC_ENABLED=False) + out = svc.RBACService.MyPermissions.get("tenant-1", "acct-1", session=sqlite_session) mock_send.assert_not_called() assert out.workspace.permission_keys == [] @@ -688,8 +695,7 @@ class TestMyPermissions: "dataset": {"default_permission_keys": [], "overrides": []}, } - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", True): - out = svc.RBACService.MyPermissions.get("tenant-1", "acct-1", app_id="app-1", session=sqlite_session) + out = svc.RBACService.MyPermissions.get("tenant-1", "acct-1", app_id="app-1", session=sqlite_session) call = _call_args(mock_send) assert call.method == "GET" @@ -700,6 +706,10 @@ class TestMyPermissions: @pytest.mark.parametrize("sqlite_session", [(TenantAccountJoin,)], indirect=True) class TestMemberRoles: + @pytest.fixture(autouse=True) + def _rbac_enabled(self, config_overrides) -> None: + config_overrides(RBAC_ENABLED=True) + def test_get(self, mock_send: MagicMock, sqlite_session: Session): mock_send.return_value = { "account_id": "acct-2", @@ -711,8 +721,7 @@ class TestMemberRoles: } ], } - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", True): - out = svc.RBACService.MemberRoles.get("tenant-1", "acct-1", "acct-2", session=sqlite_session) + out = svc.RBACService.MemberRoles.get("tenant-1", "acct-1", "acct-2", session=sqlite_session) call = _call_args(mock_send) assert call.method == "GET" assert call.endpoint == "/rbac/members/rbac-roles" @@ -720,14 +729,16 @@ class TestMemberRoles: assert out.account_id == "acct-2" assert out.roles[0].name == "Member" - def test_get_legacy_role_includes_permission_keys(self, mock_send: MagicMock, sqlite_session: Session): + def test_get_legacy_role_includes_permission_keys( + self, mock_send: MagicMock, sqlite_session: Session, config_overrides + ): + config_overrides(RBAC_ENABLED=False) sqlite_session.add( TenantAccountJoin(tenant_id="tenant-1", account_id="acct-2", role=svc.TenantAccountRole.EDITOR) ) sqlite_session.commit() - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", False): - out = svc.RBACService.MemberRoles.get("tenant-1", "acct-1", "acct-2", session=sqlite_session) + out = svc.RBACService.MemberRoles.get("tenant-1", "acct-1", "acct-2", session=sqlite_session) mock_send.assert_not_called() assert out.account_id == "acct-2" @@ -748,31 +759,32 @@ class TestMemberRoles: def test_replace(self, mock_send: MagicMock, sqlite_session: Session): mock_send.return_value = {"account_id": "acct-2", "roles": []} - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", True): - svc.RBACService.MemberRoles.replace( - "tenant-1", - "acct-1", - "acct-2", - role_ids=["workspace.owner", "workspace.editor"], - session=sqlite_session, - ) + svc.RBACService.MemberRoles.replace( + "tenant-1", + "acct-1", + "acct-2", + role_ids=["workspace.owner", "workspace.editor"], + session=sqlite_session, + ) call = _call_args(mock_send) assert call.method == "PUT" assert call.endpoint == "/rbac/members/rbac-roles" assert call.params == {"account_id": "acct-2"} assert call.json == {"role_ids": ["workspace.owner", "workspace.editor"]} - def test_replace_commits_legacy_join_role_when_rbac_disabled(self, mock_send: MagicMock, sqlite_session: Session): + def test_replace_commits_legacy_join_role_when_rbac_disabled( + self, mock_send: MagicMock, sqlite_session: Session, config_overrides + ): + config_overrides(RBAC_ENABLED=False) target_join = TenantAccountJoin(tenant_id="tenant-1", account_id="acct-2", role=svc.TenantAccountRole.NORMAL) sqlite_session.add(target_join) sqlite_session.commit() target_join_id = target_join.id engine = sqlite_session.get_bind() - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", False): - out = svc.RBACService.MemberRoles.replace( - "tenant-1", "acct-1", "acct-2", role_ids=["editor"], session=sqlite_session - ) + out = svc.RBACService.MemberRoles.replace( + "tenant-1", "acct-1", "acct-2", role_ids=["editor"], session=sqlite_session + ) mock_send.assert_not_called() # Closing the writer rolls back any uncommitted update and prevents its identity map @@ -789,17 +801,17 @@ class TestMemberRoles: assert "app.acl.preview" in out.roles[0].permission_keys def test_replace_legacy_owner_demotes_current_owner_when_rbac_disabled( - self, mock_send: MagicMock, sqlite_session: Session + self, mock_send: MagicMock, sqlite_session: Session, config_overrides ): + config_overrides(RBAC_ENABLED=False) target_join = TenantAccountJoin(tenant_id="tenant-1", account_id="acct-2", role=svc.TenantAccountRole.NORMAL) owner_join = TenantAccountJoin(tenant_id="tenant-1", account_id="acct-owner", role=svc.TenantAccountRole.OWNER) sqlite_session.add_all([target_join, owner_join]) sqlite_session.commit() - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", False): - out = svc.RBACService.MemberRoles.replace( - "tenant-1", "acct-1", "acct-2", role_ids=["owner"], session=sqlite_session - ) + out = svc.RBACService.MemberRoles.replace( + "tenant-1", "acct-1", "acct-2", role_ids=["owner"], session=sqlite_session + ) mock_send.assert_not_called() persisted_joins = { @@ -837,6 +849,10 @@ class TestMemberRoles: @pytest.mark.parametrize("sqlite_session", [(TenantAccountJoin,)], indirect=True) class TestResourcePermissions: + @pytest.fixture(autouse=True) + def _rbac_enabled(self, config_overrides) -> None: + config_overrides(RBAC_ENABLED=True) + def test_app_permissions_batch_get(self, mock_send: MagicMock, sqlite_session: Session): mock_send.return_value = { "data": [ @@ -848,10 +864,7 @@ class TestResourcePermissions: ] } - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", True): - out = svc.RBACService.AppPermissions.batch_get( - "tenant-1", "acct-1", ["app-1", "app-2"], session=sqlite_session - ) + out = svc.RBACService.AppPermissions.batch_get("tenant-1", "acct-1", ["app-1", "app-2"], session=sqlite_session) call = _call_args(mock_send) assert call.method == "POST" @@ -863,16 +876,14 @@ class TestResourcePermissions: } def test_app_permissions_batch_get_uses_legacy_role_permissions_when_rbac_disabled( - self, mock_send: MagicMock, sqlite_session: Session + self, mock_send: MagicMock, sqlite_session: Session, config_overrides ): + config_overrides(RBAC_ENABLED=False) sqlite_session.add( TenantAccountJoin(tenant_id="tenant-1", account_id="acct-1", role=svc.TenantAccountRole.EDITOR) ) sqlite_session.commit() - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", False): - out = svc.RBACService.AppPermissions.batch_get( - "tenant-1", "acct-1", ["app-1", "app-2"], session=sqlite_session - ) + out = svc.RBACService.AppPermissions.batch_get("tenant-1", "acct-1", ["app-1", "app-2"], session=sqlite_session) mock_send.assert_not_called() assert out == { @@ -889,10 +900,9 @@ class TestResourcePermissions: ] } - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", True): - out = svc.RBACService.DatasetPermissions.batch_get( - "tenant-1", "acct-1", ["ds-1", "ds-2"], session=sqlite_session - ) + out = svc.RBACService.DatasetPermissions.batch_get( + "tenant-1", "acct-1", ["ds-1", "ds-2"], session=sqlite_session + ) call = _call_args(mock_send) assert call.method == "POST" @@ -904,8 +914,9 @@ class TestResourcePermissions: } def test_dataset_permissions_batch_get_uses_legacy_role_permissions_when_rbac_disabled( - self, mock_send: MagicMock, sqlite_session: Session + self, mock_send: MagicMock, sqlite_session: Session, config_overrides ): + config_overrides(RBAC_ENABLED=False) sqlite_session.add( TenantAccountJoin( tenant_id="tenant-1", @@ -914,10 +925,9 @@ class TestResourcePermissions: ) ) sqlite_session.commit() - with patch(f"{MODULE}.dify_config.RBAC_ENABLED", False): - out = svc.RBACService.DatasetPermissions.batch_get( - "tenant-1", "acct-1", ["ds-1", "ds-2"], session=sqlite_session - ) + out = svc.RBACService.DatasetPermissions.batch_get( + "tenant-1", "acct-1", ["ds-1", "ds-2"], session=sqlite_session + ) mock_send.assert_not_called() assert out == { diff --git a/api/tests/unit_tests/services/workflow/test_queue_dispatcher.py b/api/tests/unit_tests/services/workflow/test_queue_dispatcher.py index 78cce598eb6..fef92191bba 100644 --- a/api/tests/unit_tests/services/workflow/test_queue_dispatcher.py +++ b/api/tests/unit_tests/services/workflow/test_queue_dispatcher.py @@ -1,5 +1,7 @@ from unittest.mock import patch +import pytest + from enums import DeploymentEdition from services.workflow.queue_dispatcher import ( ProfessionalQueueDispatcher, @@ -35,10 +37,12 @@ class TestDispatchers: class TestQueueDispatcherManager: + @pytest.fixture(autouse=True) + def _cloud_edition(self, config_overrides) -> None: + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD) + @patch("services.workflow.queue_dispatcher.BillingService") - @patch("services.workflow.queue_dispatcher.dify_config") - def test_cloud_edition_professional_plan(self, mock_config, mock_billing): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD + def test_cloud_edition_professional_plan(self, mock_billing): mock_billing.get_info.return_value = {"subscription": {"plan": "professional"}} dispatcher = QueueDispatcherManager.get_dispatcher("tenant-1") @@ -46,9 +50,7 @@ class TestQueueDispatcherManager: assert isinstance(dispatcher, ProfessionalQueueDispatcher) @patch("services.workflow.queue_dispatcher.BillingService") - @patch("services.workflow.queue_dispatcher.dify_config") - def test_cloud_edition_team_plan(self, mock_config, mock_billing): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD + def test_cloud_edition_team_plan(self, mock_billing): mock_billing.get_info.return_value = {"subscription": {"plan": "team"}} dispatcher = QueueDispatcherManager.get_dispatcher("tenant-1") @@ -56,9 +58,7 @@ class TestQueueDispatcherManager: assert isinstance(dispatcher, TeamQueueDispatcher) @patch("services.workflow.queue_dispatcher.BillingService") - @patch("services.workflow.queue_dispatcher.dify_config") - def test_cloud_edition_sandbox_plan(self, mock_config, mock_billing): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD + def test_cloud_edition_sandbox_plan(self, mock_billing): mock_billing.get_info.return_value = {"subscription": {"plan": "sandbox"}} dispatcher = QueueDispatcherManager.get_dispatcher("tenant-1") @@ -66,9 +66,7 @@ class TestQueueDispatcherManager: assert isinstance(dispatcher, SandboxQueueDispatcher) @patch("services.workflow.queue_dispatcher.BillingService") - @patch("services.workflow.queue_dispatcher.dify_config") - def test_cloud_edition_unknown_plan_defaults_to_sandbox(self, mock_config, mock_billing): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD + def test_cloud_edition_unknown_plan_defaults_to_sandbox(self, mock_billing): mock_billing.get_info.return_value = {"subscription": {"plan": "enterprise"}} dispatcher = QueueDispatcherManager.get_dispatcher("tenant-1") @@ -76,27 +74,22 @@ class TestQueueDispatcherManager: assert isinstance(dispatcher, SandboxQueueDispatcher) @patch("services.workflow.queue_dispatcher.BillingService") - @patch("services.workflow.queue_dispatcher.dify_config") - def test_cloud_edition_billing_failure_defaults_to_sandbox(self, mock_config, mock_billing): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD + def test_cloud_edition_billing_failure_defaults_to_sandbox(self, mock_billing): mock_billing.get_info.side_effect = Exception("billing unavailable") dispatcher = QueueDispatcherManager.get_dispatcher("tenant-1") assert isinstance(dispatcher, SandboxQueueDispatcher) - @patch("services.workflow.queue_dispatcher.dify_config") - def test_non_cloud_edition_defaults_to_team(self, mock_config): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.COMMUNITY + def test_non_cloud_edition_defaults_to_team(self, config_overrides): + config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY) dispatcher = QueueDispatcherManager.get_dispatcher("tenant-1") assert isinstance(dispatcher, TeamQueueDispatcher) @patch("services.workflow.queue_dispatcher.BillingService") - @patch("services.workflow.queue_dispatcher.dify_config") - def test_missing_subscription_key_defaults_to_sandbox(self, mock_config, mock_billing): - mock_config.DEPLOYMENT_EDITION = DeploymentEdition.CLOUD + def test_missing_subscription_key_defaults_to_sandbox(self, mock_billing): mock_billing.get_info.return_value = {} dispatcher = QueueDispatcherManager.get_dispatcher("tenant-1")