Merge commit 'fb41b215' into sandboxed-agent-rebase

Made-with: Cursor

# Conflicts:
#	.devcontainer/post_create_command.sh
#	api/commands.py
#	api/core/agent/cot_agent_runner.py
#	api/core/agent/fc_agent_runner.py
#	api/core/app/apps/workflow_app_runner.py
#	api/core/app/entities/queue_entities.py
#	api/core/app/entities/task_entities.py
#	api/core/workflow/workflow_entry.py
#	api/dify_graph/enums.py
#	api/dify_graph/graph/graph.py
#	api/dify_graph/graph_events/node.py
#	api/dify_graph/model_runtime/entities/message_entities.py
#	api/dify_graph/node_events/node.py
#	api/dify_graph/nodes/agent/agent_node.py
#	api/dify_graph/nodes/base/__init__.py
#	api/dify_graph/nodes/base/entities.py
#	api/dify_graph/nodes/base/node.py
#	api/dify_graph/nodes/llm/entities.py
#	api/dify_graph/nodes/llm/node.py
#	api/dify_graph/nodes/tool/tool_node.py
#	api/pyproject.toml
#	api/uv.lock
#	web/app/components/base/avatar/__tests__/index.spec.tsx
#	web/app/components/base/avatar/index.tsx
#	web/app/components/base/date-and-time-picker/time-picker/__tests__/index.spec.tsx
#	web/app/components/base/file-uploader/file-from-link-or-local/index.tsx
#	web/app/components/base/prompt-editor/index.tsx
#	web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx
#	web/app/components/header/account-dropdown/index.spec.tsx
#	web/app/components/share/text-generation/index.tsx
#	web/app/components/workflow/block-selector/tool/action-item.tsx
#	web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx
#	web/app/components/workflow/hooks/use-edges-interactions.ts
#	web/app/components/workflow/hooks/use-nodes-interactions.ts
#	web/app/components/workflow/index.tsx
#	web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx
#	web/app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx
#	web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/email-item.tsx
#	web/app/components/workflow/nodes/loop/use-interactions.ts
#	web/contract/router.ts
#	web/env.ts
#	web/eslint-suppressions.json
#	web/package.json
#	web/pnpm-lock.yaml
This commit is contained in:
Novice
2026-03-23 10:52:06 +08:00
1395 changed files with 167201 additions and 73658 deletions

View File

@ -20,6 +20,7 @@ from services.errors.account import (
TenantNotFoundError,
)
from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkspacesLimitExceededError
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestAccountService:
@ -53,7 +54,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -133,7 +134,7 @@ class TestAccountService:
email=email,
name=name,
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
def test_create_account_email_in_freeze(
@ -145,7 +146,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = True
@ -169,7 +170,7 @@ class TestAccountService:
"""
fake = Faker()
email = fake.email()
password = fake.password(length=12)
password = generate_valid_password(fake)
with pytest.raises(AccountPasswordError):
AccountService.authenticate(email, password)
@ -180,7 +181,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -208,8 +209,8 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
correct_password = fake.password(length=12)
wrong_password = fake.password(length=12)
correct_password = generate_valid_password(fake)
wrong_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -234,7 +235,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
new_password = fake.password(length=12)
new_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -267,7 +268,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -297,8 +298,8 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
old_password = fake.password(length=12)
new_password = fake.password(length=12)
old_password = generate_valid_password(fake)
new_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -327,9 +328,9 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
old_password = fake.password(length=12)
wrong_password = fake.password(length=12)
new_password = fake.password(length=12)
old_password = generate_valid_password(fake)
wrong_password = generate_valid_password(fake)
new_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -354,7 +355,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
old_password = fake.password(length=12)
old_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -378,7 +379,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies[
@ -412,7 +413,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies[
@ -437,7 +438,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies[
@ -535,7 +536,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -563,7 +564,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
updated_name = fake.name()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -592,7 +593,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -615,7 +616,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
ip_address = fake.ipv4()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -645,7 +646,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
ip_address = fake.ipv4()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -684,7 +685,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -714,7 +715,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -747,7 +748,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
tenant_name = fake.company()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -792,7 +793,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -825,7 +826,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
tenant_name = fake.company()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -864,7 +865,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -892,7 +893,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -926,7 +927,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
tenant_name = fake.company()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -957,7 +958,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -997,7 +998,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -1043,7 +1044,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -1080,7 +1081,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -1110,7 +1111,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -1139,7 +1140,7 @@ class TestAccountService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
wrong_code = fake.numerify(text="######")
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -1259,7 +1260,7 @@ class TestTenantService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1291,10 +1292,10 @@ class TestTenantService:
tenant_name = fake.company()
email1 = fake.email()
name1 = fake.name()
password1 = fake.password(length=12)
password1 = generate_valid_password(fake)
email2 = fake.email()
name2 = fake.name()
password2 = fake.password(length=12)
password2 = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1332,7 +1333,7 @@ class TestTenantService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1364,7 +1365,7 @@ class TestTenantService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
tenant1_name = fake.company()
tenant2_name = fake.company()
# Setup mocks
@ -1403,7 +1404,7 @@ class TestTenantService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
tenant_name = fake.company()
# Setup mocks
mock_external_service_dependencies[
@ -1441,7 +1442,7 @@ class TestTenantService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1466,7 +1467,7 @@ class TestTenantService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
tenant1_name = fake.company()
tenant2_name = fake.company()
# Setup mocks
@ -1507,7 +1508,7 @@ class TestTenantService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1534,7 +1535,7 @@ class TestTenantService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
tenant_name = fake.company()
# Setup mocks
mock_external_service_dependencies[
@ -1562,10 +1563,10 @@ class TestTenantService:
tenant_name = fake.company()
owner_email = fake.email()
owner_name = fake.name()
owner_password = fake.password(length=12)
owner_password = generate_valid_password(fake)
admin_email = fake.email()
admin_name = fake.name()
admin_password = fake.password(length=12)
admin_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1631,7 +1632,7 @@ class TestTenantService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1664,10 +1665,10 @@ class TestTenantService:
tenant_name = fake.company()
owner_email = fake.email()
owner_name = fake.name()
owner_password = fake.password(length=12)
owner_password = generate_valid_password(fake)
member_email = fake.email()
member_name = fake.name()
member_password = fake.password(length=12)
member_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1705,7 +1706,7 @@ class TestTenantService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
invalid_action = "invalid_action_that_doesnt_exist"
# Setup mocks
mock_external_service_dependencies[
@ -1738,7 +1739,7 @@ class TestTenantService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1770,10 +1771,10 @@ class TestTenantService:
tenant_name = fake.company()
owner_email = fake.email()
owner_name = fake.name()
owner_password = fake.password(length=12)
owner_password = generate_valid_password(fake)
member_email = fake.email()
member_name = fake.name()
member_password = fake.password(length=12)
member_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1829,7 +1830,7 @@ class TestTenantService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1861,10 +1862,10 @@ class TestTenantService:
tenant_name = fake.company()
owner_email = fake.email()
owner_name = fake.name()
owner_password = fake.password(length=12)
owner_password = generate_valid_password(fake)
non_member_email = fake.email()
non_member_name = fake.name()
non_member_password = fake.password(length=12)
non_member_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1900,10 +1901,10 @@ class TestTenantService:
tenant_name = fake.company()
owner_email = fake.email()
owner_name = fake.name()
owner_password = fake.password(length=12)
owner_password = generate_valid_password(fake)
member_email = fake.email()
member_name = fake.name()
member_password = fake.password(length=12)
member_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -1949,10 +1950,10 @@ class TestTenantService:
tenant_name = fake.company()
owner_email = fake.email()
owner_name = fake.name()
owner_password = fake.password(length=12)
owner_password = generate_valid_password(fake)
member_email = fake.email()
member_name = fake.name()
member_password = fake.password(length=12)
member_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -2006,10 +2007,10 @@ class TestTenantService:
tenant_name = fake.company()
owner_email = fake.email()
owner_name = fake.name()
owner_password = fake.password(length=12)
owner_password = generate_valid_password(fake)
member_email = fake.email()
member_name = fake.name()
member_password = fake.password(length=12)
member_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -2071,7 +2072,7 @@ class TestTenantService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
workspace_name = fake.company()
# Setup mocks
mock_external_service_dependencies[
@ -2110,7 +2111,7 @@ class TestTenantService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
existing_tenant_name = fake.company()
new_workspace_name = fake.company()
# Setup mocks
@ -2151,7 +2152,7 @@ class TestTenantService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
workspace_name = fake.company()
# Setup mocks to disable workspace creation
mock_external_service_dependencies[
@ -2178,13 +2179,13 @@ class TestTenantService:
tenant_name = fake.company()
owner_email = fake.email()
owner_name = fake.name()
owner_password = fake.password(length=12)
owner_password = generate_valid_password(fake)
admin_email = fake.email()
admin_name = fake.name()
admin_password = fake.password(length=12)
admin_password = generate_valid_password(fake)
normal_email = fake.email()
normal_name = fake.name()
normal_password = fake.password(length=12)
normal_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -2244,13 +2245,13 @@ class TestTenantService:
tenant_name = fake.company()
owner_email = fake.email()
owner_name = fake.name()
owner_password = fake.password(length=12)
owner_password = generate_valid_password(fake)
operator_email = fake.email()
operator_name = fake.name()
operator_password = fake.password(length=12)
operator_password = generate_valid_password(fake)
normal_email = fake.email()
normal_name = fake.name()
normal_password = fake.password(length=12)
normal_password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies[
"feature_service"
@ -2351,7 +2352,7 @@ class TestRegisterService:
fake = Faker()
admin_email = fake.email()
admin_name = fake.name()
admin_password = fake.password(length=12)
admin_password = generate_valid_password(fake)
ip_address = fake.ipv4()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -2399,7 +2400,7 @@ class TestRegisterService:
fake = Faker()
admin_email = fake.email()
admin_name = fake.name()
admin_password = fake.password(length=12)
admin_password = generate_valid_password(fake)
ip_address = fake.ipv4()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -2440,7 +2441,7 @@ class TestRegisterService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
language = fake.random_element(elements=("en-US", "zh-CN"))
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -2531,7 +2532,7 @@ class TestRegisterService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
language = fake.random_element(elements=("en-US", "zh-CN"))
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -2576,7 +2577,7 @@ class TestRegisterService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
language = fake.random_element(elements=("en-US", "zh-CN"))
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -2614,7 +2615,7 @@ class TestRegisterService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
language = fake.random_element(elements=("en-US", "zh-CN"))
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -2653,7 +2654,7 @@ class TestRegisterService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
language = fake.random_element(elements=("en-US", "zh-CN"))
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -2690,7 +2691,7 @@ class TestRegisterService:
tenant_name = fake.company()
inviter_email = fake.email()
inviter_name = fake.name()
inviter_password = fake.password(length=12)
inviter_password = generate_valid_password(fake)
new_member_email = fake.email()
language = fake.random_element(elements=("en-US", "zh-CN"))
# Setup mocks
@ -2760,10 +2761,10 @@ class TestRegisterService:
tenant_name = fake.company()
inviter_email = fake.email()
inviter_name = fake.name()
inviter_password = fake.password(length=12)
inviter_password = generate_valid_password(fake)
existing_member_email = fake.email()
existing_member_name = fake.name()
existing_member_password = fake.password(length=12)
existing_member_password = generate_valid_password(fake)
language = fake.random_element(elements=("en-US", "zh-CN"))
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -2824,10 +2825,10 @@ class TestRegisterService:
tenant_name = fake.company()
inviter_email = fake.email()
inviter_name = fake.name()
inviter_password = fake.password(length=12)
inviter_password = generate_valid_password(fake)
existing_pending_member_email = fake.email()
existing_pending_member_name = fake.name()
existing_pending_member_password = fake.password(length=12)
existing_pending_member_password = generate_valid_password(fake)
language = fake.random_element(elements=("en-US", "zh-CN"))
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -2914,10 +2915,10 @@ class TestRegisterService:
tenant_name = fake.company()
inviter_email = fake.email()
inviter_name = fake.name()
inviter_password = fake.password(length=12)
inviter_password = generate_valid_password(fake)
already_in_tenant_email = fake.email()
already_in_tenant_name = fake.name()
already_in_tenant_password = fake.password(length=12)
already_in_tenant_password = generate_valid_password(fake)
language = fake.random_element(elements=("en-US", "zh-CN"))
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -2967,7 +2968,7 @@ class TestRegisterService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -3011,7 +3012,7 @@ class TestRegisterService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -3058,7 +3059,7 @@ class TestRegisterService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -3101,7 +3102,7 @@ class TestRegisterService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -3144,7 +3145,7 @@ class TestRegisterService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
@ -3212,7 +3213,7 @@ class TestRegisterService:
fake = Faker()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
invalid_tenant_id = fake.uuid4()
token = fake.uuid4()
# Setup mocks
@ -3263,7 +3264,7 @@ class TestRegisterService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
token = fake.uuid4()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -3313,7 +3314,7 @@ class TestRegisterService:
tenant_name = fake.company()
email = fake.email()
name = fake.name()
password = fake.password(length=12)
password = generate_valid_password(fake)
token = fake.uuid4()
# Setup mocks
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
@ -3330,7 +3331,7 @@ class TestRegisterService:
TenantService.create_tenant_member(tenant, account, role="normal")
# Change tenant status to non-normal
tenant.status = "suspended"
tenant.status = "archive"
db_session_with_containers.commit()

View File

@ -11,6 +11,7 @@ from models.model import AppModelConfig, Conversation, EndUser, Message, Message
from services.account_service import AccountService, TenantService
from services.agent_service import AgentService
from services.app_service import AppService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestAgentService:
@ -111,7 +112,7 @@ class TestAgentService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant

View File

@ -9,6 +9,7 @@ from models import Account
from models.model import MessageAnnotation
from services.annotation_service import AppAnnotationService
from services.app_service import AppService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestAnnotationService:
@ -78,7 +79,7 @@ class TestAnnotationService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant

View File

@ -7,6 +7,7 @@ from sqlalchemy.orm import Session
from models.api_based_extension import APIBasedExtension
from services.account_service import AccountService, TenantService
from services.api_based_extension_service import APIBasedExtensionService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestAPIBasedExtensionService:
@ -55,7 +56,7 @@ class TestAPIBasedExtensionService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant

View File

@ -9,6 +9,7 @@ from models.model import App, AppModelConfig
from services.account_service import AccountService, TenantService
from services.app_dsl_service import AppDslService, ImportMode, ImportStatus
from services.app_service import AppService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestAppDslService:
@ -89,7 +90,7 @@ class TestAppDslService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant

View File

@ -2,6 +2,7 @@ import uuid
from unittest.mock import ANY, MagicMock, patch
import pytest
import sqlalchemy as sa
from faker import Faker
from sqlalchemy.orm import Session
@ -10,6 +11,7 @@ from models.model import EndUser
from models.workflow import Workflow
from services.app_generate_service import AppGenerateService
from services.errors.app import WorkflowIdFormatError, WorkflowNotFoundError
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestAppGenerateService:
@ -147,7 +149,7 @@ class TestAppGenerateService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -491,20 +493,20 @@ class TestAppGenerateService:
)
# Manually set invalid mode after creation
# With EnumText, invalid values are rejected at the DB level during flush,
# raising StatementError wrapping ValueError
app.mode = "invalid_mode"
# Setup test arguments
args = {"inputs": {"query": fake.text(max_nb_chars=50)}, "response_mode": "streaming"}
# Execute the method under test and expect ValueError
with pytest.raises(ValueError) as exc_info:
# Execute the method under test and expect either ValueError (direct) or
# StatementError (from EnumText validation during autoflush)
with pytest.raises((ValueError, sa.exc.StatementError)):
AppGenerateService.generate(
app_model=app, user=account, args=args, invoke_from=InvokeFrom.SERVICE_API, streaming=True
)
# Verify error message
assert "Invalid app mode" in str(exc_info.value)
def test_generate_with_workflow_id_format_error(
self, db_session_with_containers: Session, mock_external_service_dependencies
):

View File

@ -8,6 +8,7 @@ from constants.model_template import default_app_templates
from models import Account
from models.model import App, Site
from services.account_service import AccountService, TenantService
from tests.test_containers_integration_tests.helpers import generate_valid_password
# Delay import of AppService to avoid circular dependency
# from services.app_service import AppService
@ -56,7 +57,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -112,7 +113,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -155,7 +156,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -203,7 +204,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -259,7 +260,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -334,7 +335,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -404,7 +405,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -473,7 +474,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -526,7 +527,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -585,7 +586,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -645,7 +646,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -705,7 +706,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -756,7 +757,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -808,7 +809,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -868,7 +869,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -907,7 +908,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -947,7 +948,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -997,7 +998,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -1039,7 +1040,7 @@ class TestAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant

View File

@ -0,0 +1,497 @@
"""
Container-backed integration tests for dataset permission services on the real SQL path.
This module exercises persisted DatasetPermission rows and dataset permission
checks with testcontainers-backed infrastructure instead of database-chain mocks.
"""
from uuid import uuid4
import pytest
from extensions.ext_database import db
from models import Account, Tenant, TenantAccountJoin, TenantAccountRole
from models.dataset import (
Dataset,
DatasetPermission,
DatasetPermissionEnum,
)
from services.dataset_service import DatasetPermissionService, DatasetService
from services.errors.account import NoPermissionError
class DatasetPermissionTestDataFactory:
"""Create persisted entities and request payloads for dataset permission integration tests."""
@staticmethod
def create_account_with_tenant(
role: TenantAccountRole = TenantAccountRole.NORMAL,
tenant: Tenant | None = None,
) -> tuple[Account, Tenant]:
"""Create a real account and tenant with specified role."""
account = Account(
email=f"{uuid4()}@example.com",
name=f"user-{uuid4()}",
interface_language="en-US",
status="active",
)
if tenant is None:
tenant = Tenant(name=f"tenant-{uuid4()}", status="normal")
db.session.add_all([account, tenant])
else:
db.session.add(account)
db.session.flush()
join = TenantAccountJoin(
tenant_id=tenant.id,
account_id=account.id,
role=role,
current=True,
)
db.session.add(join)
db.session.commit()
account.current_tenant = tenant
return account, tenant
@staticmethod
def create_dataset(
tenant_id: str,
created_by: str,
permission: DatasetPermissionEnum = DatasetPermissionEnum.ONLY_ME,
name: str = "Test Dataset",
) -> Dataset:
"""Create a real dataset with specified attributes."""
dataset = Dataset(
tenant_id=tenant_id,
name=name,
description="desc",
data_source_type="upload_file",
indexing_technique="high_quality",
created_by=created_by,
permission=permission,
provider="vendor",
retrieval_model={"top_k": 2},
)
db.session.add(dataset)
db.session.commit()
return dataset
@staticmethod
def create_dataset_permission(
dataset_id: str,
account_id: str,
tenant_id: str,
has_permission: bool = True,
) -> DatasetPermission:
"""Create a real DatasetPermission instance."""
permission = DatasetPermission(
dataset_id=dataset_id,
account_id=account_id,
tenant_id=tenant_id,
has_permission=has_permission,
)
db.session.add(permission)
db.session.commit()
return permission
@staticmethod
def build_user_list_payload(user_ids: list[str]) -> list[dict[str, str]]:
"""Build the request payload shape used by partial-member list updates."""
return [{"user_id": user_id} for user_id in user_ids]
class TestDatasetPermissionServiceGetPartialMemberList:
"""Verify partial-member list reads against persisted DatasetPermission rows."""
def test_get_dataset_partial_member_list_with_members(self, db_session_with_containers):
"""
Test retrieving partial member list with multiple members.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
user_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
user_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
user_3, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
expected_account_ids = [user_1.id, user_2.id, user_3.id]
for account_id in expected_account_ids:
DatasetPermissionTestDataFactory.create_dataset_permission(dataset.id, account_id, tenant.id)
# Act
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
# Assert
assert set(result) == set(expected_account_ids)
assert len(result) == 3
def test_get_dataset_partial_member_list_with_single_member(self, db_session_with_containers):
"""
Test retrieving partial member list with single member.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
expected_account_ids = [user.id]
DatasetPermissionTestDataFactory.create_dataset_permission(dataset.id, user.id, tenant.id)
# Act
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
# Assert
assert set(result) == set(expected_account_ids)
assert len(result) == 1
def test_get_dataset_partial_member_list_empty(self, db_session_with_containers):
"""
Test retrieving partial member list when no members exist.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
# Act
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
# Assert
assert result == []
assert len(result) == 0
class TestDatasetPermissionServiceUpdatePartialMemberList:
"""Verify partial-member list updates against persisted DatasetPermission rows."""
def test_update_partial_member_list_add_new_members(self, db_session_with_containers):
"""
Test adding new partial members to a dataset.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
user_list = DatasetPermissionTestDataFactory.build_user_list_payload([member_1.id, member_2.id])
# Act
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, user_list)
# Assert
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
assert set(result) == {member_1.id, member_2.id}
def test_update_partial_member_list_replace_existing(self, db_session_with_containers):
"""
Test replacing existing partial members with new ones.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
old_member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
old_member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
new_member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
new_member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
old_users = DatasetPermissionTestDataFactory.build_user_list_payload([old_member_1.id, old_member_2.id])
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, old_users)
new_users = DatasetPermissionTestDataFactory.build_user_list_payload([new_member_1.id, new_member_2.id])
# Act
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, new_users)
# Assert
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
assert set(result) == {new_member_1.id, new_member_2.id}
def test_update_partial_member_list_empty_list(self, db_session_with_containers):
"""
Test updating with empty member list (clearing all members).
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
users = DatasetPermissionTestDataFactory.build_user_list_payload([member_1.id, member_2.id])
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, users)
# Act
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, [])
# Assert
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
assert result == []
def test_update_partial_member_list_database_error_rollback(self, db_session_with_containers):
"""
Test error handling and rollback on database error.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
existing_member, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
replacement_member, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
DatasetPermissionService.update_partial_member_list(
tenant.id,
dataset.id,
DatasetPermissionTestDataFactory.build_user_list_payload([existing_member.id]),
)
user_list = DatasetPermissionTestDataFactory.build_user_list_payload([replacement_member.id])
rollback_called = {"count": 0}
original_rollback = db.session.rollback
# Act / Assert
with pytest.MonkeyPatch.context() as mp:
def _raise_commit():
raise Exception("Database connection error")
def _rollback_and_mark():
rollback_called["count"] += 1
original_rollback()
mp.setattr("services.dataset_service.db.session.commit", _raise_commit)
mp.setattr("services.dataset_service.db.session.rollback", _rollback_and_mark)
with pytest.raises(Exception, match="Database connection error"):
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, user_list)
# Assert
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
assert rollback_called["count"] == 1
assert result == [existing_member.id]
assert db_session_with_containers.query(DatasetPermission).filter_by(dataset_id=dataset.id).count() == 1
class TestDatasetPermissionServiceClearPartialMemberList:
"""Verify partial-member clearing against persisted DatasetPermission rows."""
def test_clear_partial_member_list_success(self, db_session_with_containers):
"""
Test successful clearing of partial member list.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
users = DatasetPermissionTestDataFactory.build_user_list_payload([member_1.id, member_2.id])
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, users)
# Act
DatasetPermissionService.clear_partial_member_list(dataset.id)
# Assert
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
assert result == []
def test_clear_partial_member_list_empty_list(self, db_session_with_containers):
"""
Test clearing partial member list when no members exist.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
# Act
DatasetPermissionService.clear_partial_member_list(dataset.id)
# Assert
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
assert result == []
def test_clear_partial_member_list_database_error_rollback(self, db_session_with_containers):
"""
Test error handling and rollback on database error.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
users = DatasetPermissionTestDataFactory.build_user_list_payload([member_1.id, member_2.id])
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, users)
rollback_called = {"count": 0}
original_rollback = db.session.rollback
# Act / Assert
with pytest.MonkeyPatch.context() as mp:
def _raise_commit():
raise Exception("Database connection error")
def _rollback_and_mark():
rollback_called["count"] += 1
original_rollback()
mp.setattr("services.dataset_service.db.session.commit", _raise_commit)
mp.setattr("services.dataset_service.db.session.rollback", _rollback_and_mark)
with pytest.raises(Exception, match="Database connection error"):
DatasetPermissionService.clear_partial_member_list(dataset.id)
# Assert
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
assert rollback_called["count"] == 1
assert set(result) == {member_1.id, member_2.id}
assert db_session_with_containers.query(DatasetPermission).filter_by(dataset_id=dataset.id).count() == 2
class TestDatasetServiceCheckDatasetPermission:
"""Verify dataset access checks against persisted partial-member permissions."""
def test_check_dataset_permission_partial_members_with_permission_success(self, db_session_with_containers):
"""
Test that user with explicit permission can access partial_members dataset.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(
tenant.id,
owner.id,
permission=DatasetPermissionEnum.PARTIAL_TEAM,
)
DatasetPermissionTestDataFactory.create_dataset_permission(dataset.id, user.id, tenant.id)
# Act (should not raise)
DatasetService.check_dataset_permission(dataset, user)
# Assert
permissions = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
assert user.id in permissions
def test_check_dataset_permission_partial_members_without_permission_error(self, db_session_with_containers):
"""
Test error when user without permission tries to access partial_members dataset.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(
tenant.id,
owner.id,
permission=DatasetPermissionEnum.PARTIAL_TEAM,
)
# Act & Assert
with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset"):
DatasetService.check_dataset_permission(dataset, user)
class TestDatasetServiceCheckDatasetOperatorPermission:
"""Verify operator permission checks against persisted partial-member permissions."""
def test_check_dataset_operator_permission_partial_members_with_permission_success(
self, db_session_with_containers
):
"""
Test that user with explicit permission can access partial_members dataset.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(
tenant.id,
owner.id,
permission=DatasetPermissionEnum.PARTIAL_TEAM,
)
DatasetPermissionTestDataFactory.create_dataset_permission(dataset.id, user.id, tenant.id)
# Act (should not raise)
DatasetService.check_dataset_operator_permission(user=user, dataset=dataset)
# Assert
permissions = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
assert user.id in permissions
def test_check_dataset_operator_permission_partial_members_without_permission_error(
self, db_session_with_containers
):
"""
Test error when user without permission tries to access partial_members dataset.
"""
# Arrange
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
role=TenantAccountRole.NORMAL,
tenant=tenant,
)
dataset = DatasetPermissionTestDataFactory.create_dataset(
tenant.id,
owner.id,
permission=DatasetPermissionEnum.PARTIAL_TEAM,
)
# Act & Assert
with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset"):
DatasetService.check_dataset_operator_permission(user=user, dataset=dataset)

View File

@ -0,0 +1,244 @@
"""Container-backed integration tests for DatasetService.delete_dataset real SQL paths."""
from unittest.mock import patch
from uuid import uuid4
from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole
from models.dataset import Dataset, Document
from services.dataset_service import DatasetService
class DatasetDeleteIntegrationDataFactory:
"""Create persisted entities used by delete_dataset integration tests."""
@staticmethod
def create_account_with_tenant(db_session_with_containers) -> tuple[Account, Tenant]:
"""Persist an owner account, tenant, and tenant join for dataset deletion tests."""
account = Account(
email=f"owner-{uuid4()}@example.com",
name="Owner",
interface_language="en-US",
status="active",
)
db_session_with_containers.add(account)
db_session_with_containers.commit()
tenant = Tenant(
name=f"tenant-{uuid4()}",
status="normal",
)
db_session_with_containers.add(tenant)
db_session_with_containers.commit()
join = TenantAccountJoin(
tenant_id=tenant.id,
account_id=account.id,
role=TenantAccountRole.OWNER,
current=True,
)
db_session_with_containers.add(join)
db_session_with_containers.commit()
account.current_tenant = tenant
return account, tenant
@staticmethod
def create_dataset(
db_session_with_containers,
tenant_id: str,
created_by: str,
*,
indexing_technique: str | None,
chunk_structure: str | None,
index_struct: str | None = '{"type": "paragraph"}',
collection_binding_id: str | None = None,
pipeline_id: str | None = None,
) -> Dataset:
"""Persist a dataset with delete_dataset-relevant fields configured."""
dataset = Dataset(
tenant_id=tenant_id,
name=f"dataset-{uuid4()}",
data_source_type="upload_file",
indexing_technique=indexing_technique,
index_struct=index_struct,
created_by=created_by,
collection_binding_id=collection_binding_id,
pipeline_id=pipeline_id,
chunk_structure=chunk_structure,
)
db_session_with_containers.add(dataset)
db_session_with_containers.commit()
return dataset
@staticmethod
def create_document(
db_session_with_containers,
*,
tenant_id: str,
dataset_id: str,
created_by: str,
doc_form: str = "text_model",
) -> Document:
"""Persist a document so dataset.doc_form resolves through the real document path."""
document = Document(
tenant_id=tenant_id,
dataset_id=dataset_id,
position=1,
data_source_type="upload_file",
batch=f"batch-{uuid4()}",
name="Document",
created_from="upload_file",
created_by=created_by,
doc_form=doc_form,
)
db_session_with_containers.add(document)
db_session_with_containers.commit()
return document
class TestDatasetServiceDeleteDataset:
"""Integration coverage for DatasetService.delete_dataset using testcontainers."""
def test_delete_dataset_with_documents_success(self, db_session_with_containers):
"""Delete a dataset with documents and dispatch cleanup through the real signal handler."""
# Arrange
owner, tenant = DatasetDeleteIntegrationDataFactory.create_account_with_tenant(db_session_with_containers)
dataset = DatasetDeleteIntegrationDataFactory.create_dataset(
db_session_with_containers,
tenant_id=tenant.id,
created_by=owner.id,
indexing_technique="high_quality",
chunk_structure=None,
index_struct='{"type": "paragraph"}',
collection_binding_id=str(uuid4()),
pipeline_id=str(uuid4()),
)
DatasetDeleteIntegrationDataFactory.create_document(
db_session_with_containers,
tenant_id=tenant.id,
dataset_id=dataset.id,
created_by=owner.id,
doc_form="text_model",
)
# Act
with patch(
"events.event_handlers.clean_when_dataset_deleted.clean_dataset_task.delay",
autospec=True,
) as clean_dataset_delay:
result = DatasetService.delete_dataset(dataset.id, owner)
# Assert
db_session_with_containers.expire_all()
assert result is True
assert db_session_with_containers.get(Dataset, dataset.id) is None
clean_dataset_delay.assert_called_once_with(
dataset.id,
dataset.tenant_id,
dataset.indexing_technique,
dataset.index_struct,
dataset.collection_binding_id,
dataset.doc_form,
dataset.pipeline_id,
)
def test_delete_empty_dataset_success(self, db_session_with_containers):
"""Delete an empty dataset without scheduling cleanup when both gating fields are absent."""
# Arrange
owner, tenant = DatasetDeleteIntegrationDataFactory.create_account_with_tenant(db_session_with_containers)
dataset = DatasetDeleteIntegrationDataFactory.create_dataset(
db_session_with_containers,
tenant_id=tenant.id,
created_by=owner.id,
indexing_technique=None,
chunk_structure=None,
index_struct=None,
collection_binding_id=None,
pipeline_id=None,
)
# Act
with patch(
"events.event_handlers.clean_when_dataset_deleted.clean_dataset_task.delay",
autospec=True,
) as clean_dataset_delay:
result = DatasetService.delete_dataset(dataset.id, owner)
# Assert
db_session_with_containers.expire_all()
assert result is True
assert db_session_with_containers.get(Dataset, dataset.id) is None
clean_dataset_delay.assert_not_called()
def test_delete_dataset_with_partial_none_values(self, db_session_with_containers):
"""Delete a dataset without cleanup when indexing_technique is missing but doc_form resolves."""
# Arrange
owner, tenant = DatasetDeleteIntegrationDataFactory.create_account_with_tenant(db_session_with_containers)
dataset = DatasetDeleteIntegrationDataFactory.create_dataset(
db_session_with_containers,
tenant_id=tenant.id,
created_by=owner.id,
indexing_technique=None,
chunk_structure="text_model",
index_struct='{"type": "paragraph"}',
collection_binding_id=str(uuid4()),
pipeline_id=str(uuid4()),
)
# Act
with patch(
"events.event_handlers.clean_when_dataset_deleted.clean_dataset_task.delay",
autospec=True,
) as clean_dataset_delay:
result = DatasetService.delete_dataset(dataset.id, owner)
# Assert
db_session_with_containers.expire_all()
assert result is True
assert db_session_with_containers.get(Dataset, dataset.id) is None
clean_dataset_delay.assert_not_called()
def test_delete_dataset_with_doc_form_none_indexing_technique_exists(self, db_session_with_containers):
"""Delete a dataset without cleanup when indexing exists but doc_form resolves to None."""
# Arrange
owner, tenant = DatasetDeleteIntegrationDataFactory.create_account_with_tenant(db_session_with_containers)
dataset = DatasetDeleteIntegrationDataFactory.create_dataset(
db_session_with_containers,
tenant_id=tenant.id,
created_by=owner.id,
indexing_technique="high_quality",
chunk_structure=None,
index_struct='{"type": "paragraph"}',
collection_binding_id=str(uuid4()),
pipeline_id=str(uuid4()),
)
# Act
with patch(
"events.event_handlers.clean_when_dataset_deleted.clean_dataset_task.delay",
autospec=True,
) as clean_dataset_delay:
result = DatasetService.delete_dataset(dataset.id, owner)
# Assert
db_session_with_containers.expire_all()
assert result is True
assert db_session_with_containers.get(Dataset, dataset.id) is None
clean_dataset_delay.assert_not_called()
def test_delete_dataset_not_found(self, db_session_with_containers):
"""Return False without scheduling cleanup when the target dataset does not exist."""
# Arrange
owner, _ = DatasetDeleteIntegrationDataFactory.create_account_with_tenant(db_session_with_containers)
missing_dataset_id = str(uuid4())
# Act
with patch(
"events.event_handlers.clean_when_dataset_deleted.clean_dataset_task.delay",
autospec=True,
) as clean_dataset_delay:
result = DatasetService.delete_dataset(missing_dataset_id, owner)
# Assert
assert result is False
clean_dataset_delay.assert_not_called()

View File

@ -263,6 +263,27 @@ class TestFileService:
user=account,
)
def test_upload_file_allows_regular_punctuation_in_filename(
self, db_session_with_containers: Session, engine, mock_external_service_dependencies
):
"""
Test file upload allows punctuation that is safe when stored as metadata.
"""
account = self._create_test_account(db_session_with_containers, mock_external_service_dependencies)
filename = 'candidate?resume for "dify"<final>|v2:.txt'
content = b"test content"
mimetype = "text/plain"
upload_file = FileService(engine).upload_file(
filename=filename,
content=content,
mimetype=mimetype,
user=account,
)
assert upload_file.name == filename
def test_upload_file_filename_too_long(
self, db_session_with_containers: Session, engine, mock_external_service_dependencies
):

View File

@ -4,7 +4,7 @@ from unittest.mock import MagicMock
import pytest
from dify_graph.enums import NodeType
from dify_graph.enums import BuiltinNodeTypes
from dify_graph.nodes.human_input.entities import (
EmailDeliveryConfig,
EmailDeliveryMethod,
@ -68,7 +68,7 @@ def _create_app_with_draft_workflow(session, *, delivery_method_id: uuid.UUID) -
inputs=[],
user_actions=[],
).model_dump(mode="json")
node_data["type"] = NodeType.HUMAN_INPUT.value
node_data["type"] = BuiltinNodeTypes.HUMAN_INPUT
graph = json.dumps({"nodes": [{"id": "human-node", "data": node_data}], "edges": []})
workflow = Workflow.new(

View File

@ -0,0 +1,233 @@
import datetime
import json
import uuid
from decimal import Decimal
import pytest
from sqlalchemy.orm import Session
from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole
from models.model import (
App,
AppAnnotationHitHistory,
Conversation,
DatasetRetrieverResource,
Message,
MessageAgentThought,
MessageAnnotation,
MessageChain,
MessageFeedback,
MessageFile,
)
from models.web import SavedMessage
from services.retention.conversation.message_export_service import AppMessageExportService, AppMessageExportStats
class TestAppMessageExportServiceIntegration:
@pytest.fixture(autouse=True)
def cleanup_database(self, db_session_with_containers: Session):
yield
db_session_with_containers.query(DatasetRetrieverResource).delete()
db_session_with_containers.query(AppAnnotationHitHistory).delete()
db_session_with_containers.query(SavedMessage).delete()
db_session_with_containers.query(MessageFile).delete()
db_session_with_containers.query(MessageAgentThought).delete()
db_session_with_containers.query(MessageChain).delete()
db_session_with_containers.query(MessageAnnotation).delete()
db_session_with_containers.query(MessageFeedback).delete()
db_session_with_containers.query(Message).delete()
db_session_with_containers.query(Conversation).delete()
db_session_with_containers.query(App).delete()
db_session_with_containers.query(TenantAccountJoin).delete()
db_session_with_containers.query(Tenant).delete()
db_session_with_containers.query(Account).delete()
db_session_with_containers.commit()
@staticmethod
def _create_app_context(session: Session) -> tuple[App, Conversation]:
account = Account(
email=f"test-{uuid.uuid4()}@example.com",
name="tester",
interface_language="en-US",
status="active",
)
session.add(account)
session.flush()
tenant = Tenant(name=f"tenant-{uuid.uuid4()}", status="normal")
session.add(tenant)
session.flush()
join = TenantAccountJoin(
tenant_id=tenant.id,
account_id=account.id,
role=TenantAccountRole.OWNER,
current=True,
)
session.add(join)
session.flush()
app = App(
tenant_id=tenant.id,
name="export-app",
description="integration test app",
mode="chat",
enable_site=True,
enable_api=True,
api_rpm=60,
api_rph=3600,
is_demo=False,
is_public=False,
created_by=account.id,
updated_by=account.id,
)
session.add(app)
session.flush()
conversation = Conversation(
app_id=app.id,
app_model_config_id=str(uuid.uuid4()),
model_provider="openai",
model_id="gpt-4o-mini",
mode="chat",
name="conv",
inputs={"seed": 1},
status="normal",
from_source="api",
from_end_user_id=str(uuid.uuid4()),
)
session.add(conversation)
session.commit()
return app, conversation
@staticmethod
def _create_message(
session: Session,
app: App,
conversation: Conversation,
created_at: datetime.datetime,
*,
query: str,
answer: str,
inputs: dict,
message_metadata: str | None,
) -> Message:
message = Message(
app_id=app.id,
conversation_id=conversation.id,
model_provider="openai",
model_id="gpt-4o-mini",
inputs=inputs,
query=query,
answer=answer,
message=[{"role": "assistant", "content": answer}],
message_tokens=10,
message_unit_price=Decimal("0.001"),
answer_tokens=20,
answer_unit_price=Decimal("0.002"),
total_price=Decimal("0.003"),
currency="USD",
message_metadata=message_metadata,
from_source="api",
from_end_user_id=conversation.from_end_user_id,
created_at=created_at,
)
session.add(message)
session.flush()
return message
def test_iter_records_with_stats(self, db_session_with_containers: Session):
app, conversation = self._create_app_context(db_session_with_containers)
first_inputs = {
"plain": "v1",
"nested": {"a": 1, "b": [1, {"x": True}]},
"list": ["x", 2, {"y": "z"}],
}
second_inputs = {"other": "value", "items": [1, 2, 3]}
base_time = datetime.datetime(2026, 2, 25, 10, 0, 0)
first_message = self._create_message(
db_session_with_containers,
app,
conversation,
created_at=base_time,
query="q1",
answer="a1",
inputs=first_inputs,
message_metadata=json.dumps({"retriever_resources": [{"dataset_id": "ds-1"}]}),
)
second_message = self._create_message(
db_session_with_containers,
app,
conversation,
created_at=base_time + datetime.timedelta(minutes=1),
query="q2",
answer="a2",
inputs=second_inputs,
message_metadata=None,
)
user_feedback_1 = MessageFeedback(
app_id=app.id,
conversation_id=conversation.id,
message_id=first_message.id,
rating="like",
from_source="user",
content="first",
from_end_user_id=conversation.from_end_user_id,
)
user_feedback_2 = MessageFeedback(
app_id=app.id,
conversation_id=conversation.id,
message_id=first_message.id,
rating="dislike",
from_source="user",
content="second",
from_end_user_id=conversation.from_end_user_id,
)
admin_feedback = MessageFeedback(
app_id=app.id,
conversation_id=conversation.id,
message_id=first_message.id,
rating="like",
from_source="admin",
content="should-be-filtered",
from_account_id=str(uuid.uuid4()),
)
db_session_with_containers.add_all([user_feedback_1, user_feedback_2, admin_feedback])
user_feedback_1.created_at = base_time + datetime.timedelta(minutes=2)
user_feedback_2.created_at = base_time + datetime.timedelta(minutes=3)
admin_feedback.created_at = base_time + datetime.timedelta(minutes=4)
db_session_with_containers.commit()
service = AppMessageExportService(
app_id=app.id,
start_from=base_time - datetime.timedelta(minutes=1),
end_before=base_time + datetime.timedelta(minutes=10),
filename="unused",
batch_size=1,
dry_run=True,
)
stats = AppMessageExportStats()
records = list(service._iter_records_with_stats(stats))
service._finalize_stats(stats)
assert len(records) == 2
assert records[0].message_id == first_message.id
assert records[1].message_id == second_message.id
assert records[0].inputs == first_inputs
assert records[1].inputs == second_inputs
assert records[0].retriever_resources == [{"dataset_id": "ds-1"}]
assert records[1].retriever_resources == []
assert [feedback.rating for feedback in records[0].feedback] == ["like", "dislike"]
assert [feedback.content for feedback in records[0].feedback] == ["first", "second"]
assert records[1].feedback == []
assert stats.batches == 2
assert stats.total_messages == 2
assert stats.messages_with_feedback == 1
assert stats.total_feedbacks == 2

View File

@ -13,6 +13,7 @@ from services.errors.message import (
SuggestedQuestionsAfterAnswerDisabledError,
)
from services.message_service import MessageService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestMessageService:
@ -95,7 +96,7 @@ class TestMessageService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -633,7 +634,7 @@ class TestMessageService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(other_account, name=fake.company())

View File

@ -8,6 +8,7 @@ from models.model import EndUser, Message
from models.web import SavedMessage
from services.app_service import AppService
from services.saved_message_service import SavedMessageService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestSavedMessageService:
@ -64,7 +65,7 @@ class TestSavedMessageService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -162,7 +163,7 @@ class TestSavedMessageService:
answer_unit_price=0.002,
total_price=0.003,
currency="USD",
status="success",
status="normal",
)
db_session_with_containers.add(message)

View File

@ -10,6 +10,7 @@ from core.trigger.entities.entities import Subscription as TriggerSubscriptionEn
from models.provider_ids import TriggerProviderID
from models.trigger import TriggerSubscription
from services.trigger.trigger_provider_service import TriggerProviderService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestTriggerProviderService:
@ -75,7 +76,7 @@ class TestTriggerProviderService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant

View File

@ -12,6 +12,7 @@ from models.web import PinnedConversation
from services.account_service import AccountService, TenantService
from services.app_service import AppService
from services.web_conversation_service import WebConversationService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestWebConversationService:
@ -69,7 +70,7 @@ class TestWebConversationService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant

View File

@ -12,6 +12,7 @@ from models import Account, AccountStatus, Tenant, TenantAccountJoin, TenantAcco
from models.model import App, Site
from services.errors.account import AccountLoginError, AccountNotFoundError, AccountPasswordError
from services.webapp_auth_service import WebAppAuthService, WebAppAuthType
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestWebAppAuthService:
@ -109,7 +110,7 @@ class TestWebAppAuthService:
tuple: (account, tenant, password) - Created account, tenant and password
"""
fake = Faker()
password = fake.password(length=12)
password = generate_valid_password(fake)
# Create account with password
import uuid
@ -272,7 +273,7 @@ class TestWebAppAuthService:
"""
# Arrange: Create banned account
fake = Faker()
password = fake.password(length=12)
password = generate_valid_password(fake)
unique_email = f"test_{uuid.uuid4().hex[:8]}@example.com"
account = Account(

View File

@ -13,6 +13,7 @@ from models.trigger import AppTrigger, WorkflowWebhookTrigger
from models.workflow import Workflow
from services.account_service import AccountService, TenantService
from services.trigger.webhook_service import WebhookService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestWebhookService:
@ -60,7 +61,7 @@ class TestWebhookService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -172,7 +173,7 @@ class TestWebhookService:
assert workflow.app_id == test_data["app"].id
assert node_config is not None
assert node_config["id"] == "webhook_node"
assert node_config["data"]["title"] == "Test Webhook"
assert node_config["data"].title == "Test Webhook"
def test_get_webhook_trigger_and_workflow_not_found(self, flask_app_with_containers):
"""Test webhook trigger not found scenario."""

View File

@ -15,6 +15,7 @@ from services.account_service import AccountService, TenantService
# Delay import of AppService to avoid circular dependency
# from services.app_service import AppService
from services.workflow_app_service import WorkflowAppService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestWorkflowAppService:
@ -72,7 +73,7 @@ class TestWorkflowAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
@ -120,7 +121,7 @@ class TestWorkflowAppService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant

View File

@ -15,6 +15,7 @@ from models.workflow import WorkflowRun
from services.account_service import AccountService, TenantService
from services.app_service import AppService
from services.workflow_run_service import WorkflowRunService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestWorkflowRunService:
@ -72,7 +73,7 @@ class TestWorkflowRunService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant

View File

@ -62,7 +62,7 @@ class TestWorkflowService:
tenant = Tenant(
name=f"Test Tenant {fake.company()}",
plan="basic",
status="active",
status="normal",
)
tenant.id = account.current_tenant_id
tenant.created_at = fake.date_time_this_year()
@ -860,8 +860,8 @@ class TestWorkflowService:
# Act
try:
result = workflow_service.get_default_block_config(node_type=invalid_node_type)
# If we get here, the service should return None for invalid types
assert result is None
# If we get here, the service should return an empty config for invalid types.
assert result == {}
except ValueError:
# It's also acceptable for the service to raise a ValueError for invalid types
pass
@ -1090,20 +1090,19 @@ class TestWorkflowService:
This test ensures that the service correctly handles feature validation
for unsupported app modes, preventing invalid operations.
With EnumText, invalid values are rejected at the DB level during flush,
raising StatementError wrapping ValueError.
"""
# Arrange
fake = Faker()
app = self._create_test_app(db_session_with_containers, fake)
app.mode = "invalid_mode" # Invalid mode
db_session_with_containers.commit()
# Act & Assert - EnumText validation rejects invalid values at DB flush
import sqlalchemy as sa
workflow_service = WorkflowService()
features = {"test": "value"}
# Act & Assert
with pytest.raises(ValueError, match="Invalid app mode: invalid_mode"):
workflow_service.validate_features_structure(app_model=app, features=features)
with pytest.raises((ValueError, sa.exc.StatementError)):
db_session_with_containers.commit()
def test_update_workflow_success(self, db_session_with_containers: Session):
"""
@ -1429,14 +1428,14 @@ class TestWorkflowService:
import uuid
from datetime import datetime
from dify_graph.enums import NodeType, WorkflowNodeExecutionStatus
from dify_graph.enums import BuiltinNodeTypes, WorkflowNodeExecutionStatus
from dify_graph.graph_events import NodeRunSucceededEvent
from dify_graph.node_events import NodeRunResult
from dify_graph.nodes.base.node import Node
# Create mock node
mock_node = MagicMock(spec=Node)
mock_node.node_type = NodeType.START
mock_node.node_type = BuiltinNodeTypes.START
mock_node.title = "Test Node"
mock_node.error_strategy = None
@ -1453,7 +1452,7 @@ class TestWorkflowService:
mock_event = NodeRunSucceededEvent(
id=str(uuid.uuid4()),
node_id=node_id,
node_type=NodeType.START,
node_type=BuiltinNodeTypes.START,
node_run_result=mock_result,
start_at=datetime.now(),
)
@ -1474,9 +1473,9 @@ class TestWorkflowService:
# Assert
assert result is not None
assert result.node_id == node_id
from dify_graph.enums import NodeType
from dify_graph.enums import BuiltinNodeTypes
assert result.node_type == NodeType.START # Should match the mock node type
assert result.node_type == BuiltinNodeTypes.START # Should match the mock node type
assert result.title == "Test Node"
# Import the enum for comparison
from dify_graph.enums import WorkflowNodeExecutionStatus
@ -1504,14 +1503,14 @@ class TestWorkflowService:
import uuid
from datetime import datetime
from dify_graph.enums import NodeType, WorkflowNodeExecutionStatus
from dify_graph.enums import BuiltinNodeTypes, WorkflowNodeExecutionStatus
from dify_graph.graph_events import NodeRunFailedEvent
from dify_graph.node_events import NodeRunResult
from dify_graph.nodes.base.node import Node
# Create mock node
mock_node = MagicMock(spec=Node)
mock_node.node_type = NodeType.LLM
mock_node.node_type = BuiltinNodeTypes.LLM
mock_node.title = "Test Node"
mock_node.error_strategy = None
@ -1526,7 +1525,7 @@ class TestWorkflowService:
mock_event = NodeRunFailedEvent(
id=str(uuid.uuid4()),
node_id=node_id,
node_type=NodeType.LLM,
node_type=BuiltinNodeTypes.LLM,
node_run_result=mock_result,
error="Test error message",
start_at=datetime.now(),
@ -1573,14 +1572,14 @@ class TestWorkflowService:
import uuid
from datetime import datetime
from dify_graph.enums import ErrorStrategy, NodeType, WorkflowNodeExecutionStatus
from dify_graph.enums import BuiltinNodeTypes, ErrorStrategy, WorkflowNodeExecutionStatus
from dify_graph.graph_events import NodeRunFailedEvent
from dify_graph.node_events import NodeRunResult
from dify_graph.nodes.base.node import Node
# Create mock node with continue_on_error
mock_node = MagicMock(spec=Node)
mock_node.node_type = NodeType.TOOL
mock_node.node_type = BuiltinNodeTypes.TOOL
mock_node.title = "Test Node"
mock_node.error_strategy = ErrorStrategy.DEFAULT_VALUE
mock_node.default_value_dict = {"default_output": "default_value"}
@ -1596,7 +1595,7 @@ class TestWorkflowService:
mock_event = NodeRunFailedEvent(
id=str(uuid.uuid4()),
node_id=node_id,
node_type=NodeType.TOOL,
node_type=BuiltinNodeTypes.TOOL,
node_run_result=mock_result,
error="Test error message",
start_at=datetime.now(),

View File

@ -13,6 +13,7 @@ from models.workflow import Workflow as WorkflowModel
from services.account_service import AccountService, TenantService
from services.app_service import AppService
from services.tools.workflow_tools_manage_service import WorkflowToolManageService
from tests.test_containers_integration_tests.helpers import generate_valid_password
class TestWorkflowToolManageService:
@ -87,7 +88,7 @@ class TestWorkflowToolManageService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
password=fake.password(length=12),
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant