test: centralize enterprise config overrides (#40853)

This commit is contained in:
Asuka Minato authored and GitHub committed 2026-08-18 12:01:01 +00:00
1 parent c7f4422333
commit 20fdbcf36f
8 files changed
+221 -296

No files matched your search

@@ -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"
@@ -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)
@@ -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"),