mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 18:08:07 +08:00
refactor(api): continue decoupling dify_graph from API concerns (#33580)
Signed-off-by: -LAN- <laipz8200@outlook.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: WH-2099 <wh2099@pm.me>
This commit is contained in:
@ -6,6 +6,9 @@ from unittest.mock import Mock, patch
|
||||
import pytest
|
||||
from sqlalchemy import Engine
|
||||
|
||||
from core.workflow.file_reference import build_file_reference
|
||||
from dify_graph.file.enums import FileTransferMethod, FileType
|
||||
from dify_graph.file.models import File
|
||||
from dify_graph.variables.segments import ObjectSegment, StringSegment
|
||||
from dify_graph.variables.types import SegmentType
|
||||
from models.model import UploadFile
|
||||
@ -54,25 +57,18 @@ class TestDraftVarLoaderSimple:
|
||||
with patch("services.workflow_draft_variable_service.storage") as mock_storage:
|
||||
mock_storage.load.return_value = test_content.encode()
|
||||
|
||||
with patch("factories.variable_factory.segment_to_variable") as mock_segment_to_variable:
|
||||
mock_variable = Mock()
|
||||
mock_variable.id = "draft-var-id"
|
||||
mock_variable.name = "test_variable"
|
||||
mock_variable.value = StringSegment(value=test_content)
|
||||
mock_segment_to_variable.return_value = mock_variable
|
||||
# Execute the method
|
||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
||||
|
||||
# Execute the method
|
||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
||||
# Verify results
|
||||
assert selector_tuple == ("test-node-id", "test_variable")
|
||||
assert variable.id == "draft-var-id"
|
||||
assert variable.name == "test_variable"
|
||||
assert variable.description == "test description"
|
||||
assert variable.value == test_content
|
||||
|
||||
# Verify results
|
||||
assert selector_tuple == ("test-node-id", "test_variable")
|
||||
assert variable.id == "draft-var-id"
|
||||
assert variable.name == "test_variable"
|
||||
assert variable.description == "test description"
|
||||
assert variable.value == test_content
|
||||
|
||||
# Verify storage was called correctly
|
||||
mock_storage.load.assert_called_once_with("storage/key/test.txt")
|
||||
# Verify storage was called correctly
|
||||
mock_storage.load.assert_called_once_with("storage/key/test.txt")
|
||||
|
||||
def test_load_offloaded_variable_object_type_unit(self, draft_var_loader):
|
||||
"""Test _load_offloaded_variable with object type - isolated unit test."""
|
||||
@ -97,31 +93,22 @@ class TestDraftVarLoaderSimple:
|
||||
|
||||
with patch("services.workflow_draft_variable_service.storage") as mock_storage:
|
||||
mock_storage.load.return_value = test_json_content.encode()
|
||||
mock_segment = ObjectSegment(value=test_object)
|
||||
draft_var.build_segment_from_serialized_value.return_value = mock_segment
|
||||
|
||||
with patch.object(WorkflowDraftVariable, "build_segment_with_type") as mock_build_segment:
|
||||
mock_segment = ObjectSegment(value=test_object)
|
||||
mock_build_segment.return_value = mock_segment
|
||||
# Execute the method
|
||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
||||
|
||||
with patch("factories.variable_factory.segment_to_variable") as mock_segment_to_variable:
|
||||
mock_variable = Mock()
|
||||
mock_variable.id = "draft-var-id"
|
||||
mock_variable.name = "test_object"
|
||||
mock_variable.value = mock_segment
|
||||
mock_segment_to_variable.return_value = mock_variable
|
||||
# Verify results
|
||||
assert selector_tuple == ("test-node-id", "test_object")
|
||||
assert variable.id == "draft-var-id"
|
||||
assert variable.name == "test_object"
|
||||
assert variable.description == "test description"
|
||||
assert variable.value == test_object
|
||||
|
||||
# Execute the method
|
||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
||||
|
||||
# Verify results
|
||||
assert selector_tuple == ("test-node-id", "test_object")
|
||||
assert variable.id == "draft-var-id"
|
||||
assert variable.name == "test_object"
|
||||
assert variable.description == "test description"
|
||||
assert variable.value == test_object
|
||||
|
||||
# Verify method calls
|
||||
mock_storage.load.assert_called_once_with("storage/key/test.json")
|
||||
mock_build_segment.assert_called_once_with(SegmentType.OBJECT, test_object)
|
||||
# Verify method calls
|
||||
mock_storage.load.assert_called_once_with("storage/key/test.json")
|
||||
draft_var.build_segment_from_serialized_value.assert_called_once_with(SegmentType.OBJECT, test_object)
|
||||
|
||||
def test_load_offloaded_variable_missing_variable_file_unit(self, draft_var_loader):
|
||||
"""Test that assertion error is raised when variable_file is None."""
|
||||
@ -176,32 +163,23 @@ class TestDraftVarLoaderSimple:
|
||||
|
||||
with patch("services.workflow_draft_variable_service.storage") as mock_storage:
|
||||
mock_storage.load.return_value = test_json_content.encode()
|
||||
from dify_graph.variables.segments import FloatSegment
|
||||
|
||||
with patch.object(WorkflowDraftVariable, "build_segment_with_type") as mock_build_segment:
|
||||
from dify_graph.variables.segments import FloatSegment
|
||||
mock_segment = FloatSegment(value=test_number)
|
||||
draft_var.build_segment_from_serialized_value.return_value = mock_segment
|
||||
|
||||
mock_segment = FloatSegment(value=test_number)
|
||||
mock_build_segment.return_value = mock_segment
|
||||
# Execute the method
|
||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
||||
|
||||
with patch("factories.variable_factory.segment_to_variable") as mock_segment_to_variable:
|
||||
mock_variable = Mock()
|
||||
mock_variable.id = "draft-var-id"
|
||||
mock_variable.name = "test_number"
|
||||
mock_variable.value = mock_segment
|
||||
mock_segment_to_variable.return_value = mock_variable
|
||||
# Verify results
|
||||
assert selector_tuple == ("test-node-id", "test_number")
|
||||
assert variable.id == "draft-var-id"
|
||||
assert variable.name == "test_number"
|
||||
assert variable.description == "test number description"
|
||||
|
||||
# Execute the method
|
||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
||||
|
||||
# Verify results
|
||||
assert selector_tuple == ("test-node-id", "test_number")
|
||||
assert variable.id == "draft-var-id"
|
||||
assert variable.name == "test_number"
|
||||
assert variable.description == "test number description"
|
||||
|
||||
# Verify method calls
|
||||
mock_storage.load.assert_called_once_with("storage/key/test_number.json")
|
||||
mock_build_segment.assert_called_once_with(SegmentType.NUMBER, test_number)
|
||||
# Verify method calls
|
||||
mock_storage.load.assert_called_once_with("storage/key/test_number.json")
|
||||
draft_var.build_segment_from_serialized_value.assert_called_once_with(SegmentType.NUMBER, test_number)
|
||||
|
||||
def test_load_offloaded_variable_array_type_unit(self, draft_var_loader):
|
||||
"""Test _load_offloaded_variable with array type - isolated unit test."""
|
||||
@ -226,32 +204,83 @@ class TestDraftVarLoaderSimple:
|
||||
|
||||
with patch("services.workflow_draft_variable_service.storage") as mock_storage:
|
||||
mock_storage.load.return_value = test_json_content.encode()
|
||||
from dify_graph.variables.segments import ArrayAnySegment
|
||||
|
||||
with patch.object(WorkflowDraftVariable, "build_segment_with_type") as mock_build_segment:
|
||||
from dify_graph.variables.segments import ArrayAnySegment
|
||||
mock_segment = ArrayAnySegment(value=test_array)
|
||||
draft_var.build_segment_from_serialized_value.return_value = mock_segment
|
||||
|
||||
mock_segment = ArrayAnySegment(value=test_array)
|
||||
mock_build_segment.return_value = mock_segment
|
||||
# Execute the method
|
||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
||||
|
||||
with patch("factories.variable_factory.segment_to_variable") as mock_segment_to_variable:
|
||||
mock_variable = Mock()
|
||||
mock_variable.id = "draft-var-id"
|
||||
mock_variable.name = "test_array"
|
||||
mock_variable.value = mock_segment
|
||||
mock_segment_to_variable.return_value = mock_variable
|
||||
# Verify results
|
||||
assert selector_tuple == ("test-node-id", "test_array")
|
||||
assert variable.id == "draft-var-id"
|
||||
assert variable.name == "test_array"
|
||||
assert variable.description == "test array description"
|
||||
|
||||
# Execute the method
|
||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
||||
# Verify method calls
|
||||
mock_storage.load.assert_called_once_with("storage/key/test_array.json")
|
||||
draft_var.build_segment_from_serialized_value.assert_called_once_with(SegmentType.ARRAY_ANY, test_array)
|
||||
|
||||
# Verify results
|
||||
assert selector_tuple == ("test-node-id", "test_array")
|
||||
assert variable.id == "draft-var-id"
|
||||
assert variable.name == "test_array"
|
||||
assert variable.description == "test array description"
|
||||
def test_load_offloaded_variable_file_type_rebuilds_storage_backed_payload(self, draft_var_loader):
|
||||
upload_file = Mock(spec=UploadFile)
|
||||
upload_file.key = "storage/key/test_file.json"
|
||||
|
||||
# Verify method calls
|
||||
mock_storage.load.assert_called_once_with("storage/key/test_array.json")
|
||||
mock_build_segment.assert_called_once_with(SegmentType.ARRAY_ANY, test_array)
|
||||
variable_file = Mock(spec=WorkflowDraftVariableFile)
|
||||
variable_file.value_type = SegmentType.FILE
|
||||
variable_file.upload_file = upload_file
|
||||
|
||||
draft_var = WorkflowDraftVariable()
|
||||
draft_var.id = "draft-var-id"
|
||||
draft_var.app_id = "app-1"
|
||||
draft_var.node_id = "test-node-id"
|
||||
draft_var.name = "test_file"
|
||||
draft_var.description = "test file description"
|
||||
draft_var._set_selector(["test-node-id", "test_file"])
|
||||
draft_var.variable_file = variable_file
|
||||
|
||||
persisted_file = File(
|
||||
id="file-1",
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||
reference=build_file_reference(record_id="upload-1", storage_key="legacy-storage-key"),
|
||||
filename="test.txt",
|
||||
extension=".txt",
|
||||
mime_type="text/plain",
|
||||
size=12,
|
||||
)
|
||||
rebuilt_file = File(
|
||||
id="file-1",
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||
reference=build_file_reference(record_id="upload-1"),
|
||||
filename="test.txt",
|
||||
extension=".txt",
|
||||
mime_type="text/plain",
|
||||
size=12,
|
||||
storage_key="canonical-storage-key",
|
||||
)
|
||||
|
||||
raw_file = {
|
||||
**persisted_file.model_dump(mode="json"),
|
||||
"tenant_id": "legacy-tenant",
|
||||
}
|
||||
|
||||
with (
|
||||
patch("services.workflow_draft_variable_service.storage") as mock_storage,
|
||||
patch("models.workflow._resolve_workflow_app_tenant_id", return_value="tenant-1"),
|
||||
patch("models.workflow.build_file_from_stored_mapping", return_value=rebuilt_file) as rebuild_file,
|
||||
):
|
||||
mock_storage.load.return_value = json.dumps(raw_file).encode()
|
||||
|
||||
selector_tuple, variable = draft_var_loader._load_offloaded_variable(draft_var)
|
||||
|
||||
assert selector_tuple == ("test-node-id", "test_file")
|
||||
assert variable.id == "draft-var-id"
|
||||
assert variable.name == "test_file"
|
||||
assert variable.description == "test file description"
|
||||
assert variable.value == rebuilt_file
|
||||
rebuild_file.assert_called_once_with(file_mapping=raw_file, tenant_id="tenant-1")
|
||||
|
||||
def test_load_variables_with_offloaded_variables_unit(self, draft_var_loader):
|
||||
"""Test load_variables method with mix of regular and offloaded variables."""
|
||||
|
||||
@ -7,8 +7,15 @@ import pytest
|
||||
from sqlalchemy import Engine
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from dify_graph.constants import SYSTEM_VARIABLE_NODE_ID
|
||||
from dify_graph.enums import BuiltinNodeTypes, SystemVariableKey
|
||||
from core.workflow.system_variables import SystemVariableKey
|
||||
from core.workflow.variable_prefixes import (
|
||||
CONVERSATION_VARIABLE_NODE_ID,
|
||||
ENVIRONMENT_VARIABLE_NODE_ID,
|
||||
SYSTEM_VARIABLE_NODE_ID,
|
||||
)
|
||||
from dify_graph.enums import BuiltinNodeTypes
|
||||
from dify_graph.file.enums import FileTransferMethod, FileType
|
||||
from dify_graph.file.models import File
|
||||
from dify_graph.variables.segments import StringSegment
|
||||
from dify_graph.variables.types import SegmentType
|
||||
from libs.uuid_utils import uuidv7
|
||||
@ -86,6 +93,20 @@ class TestDraftVariableSaver:
|
||||
expected_node_id=_NODE_ID,
|
||||
expected_name="start_input",
|
||||
),
|
||||
TestCase(
|
||||
name="name with `env.` prefix should return the environment node_id",
|
||||
input_node_id=_NODE_ID,
|
||||
input_name="env.API_KEY",
|
||||
expected_node_id=ENVIRONMENT_VARIABLE_NODE_ID,
|
||||
expected_name="API_KEY",
|
||||
),
|
||||
TestCase(
|
||||
name="name with `conversation.` prefix should return the conversation node_id",
|
||||
input_node_id=_NODE_ID,
|
||||
input_name="conversation.session_id",
|
||||
expected_node_id=CONVERSATION_VARIABLE_NODE_ID,
|
||||
expected_name="session_id",
|
||||
),
|
||||
TestCase(
|
||||
name="dummy_variable should return the original input node_id",
|
||||
input_node_id=_NODE_ID,
|
||||
@ -112,6 +133,47 @@ class TestDraftVariableSaver:
|
||||
assert node_id == c.expected_node_id, fail_msg
|
||||
assert name == c.expected_name, fail_msg
|
||||
|
||||
def test_build_variables_from_start_mapping_rebuilds_system_files(self):
|
||||
mock_session = MagicMock(spec=Session)
|
||||
mock_user = MagicMock(spec=Account)
|
||||
mock_user.id = str(uuid.uuid4())
|
||||
saver = DraftVariableSaver(
|
||||
session=mock_session,
|
||||
app_id=self._get_test_app_id(),
|
||||
node_id="start",
|
||||
node_type=BuiltinNodeTypes.START,
|
||||
node_execution_id="exec-1",
|
||||
user=mock_user,
|
||||
)
|
||||
rebuilt_file = File(
|
||||
id="file-1",
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||
reference="upload-1",
|
||||
filename="test.txt",
|
||||
extension=".txt",
|
||||
mime_type="text/plain",
|
||||
size=12,
|
||||
storage_key="canonical-storage-key",
|
||||
)
|
||||
raw_file = {
|
||||
**rebuilt_file.model_dump(mode="json"),
|
||||
"tenant_id": "legacy-tenant",
|
||||
}
|
||||
|
||||
with (
|
||||
patch.object(saver, "_resolve_app_tenant_id", return_value="tenant-1"),
|
||||
patch(
|
||||
"services.workflow_draft_variable_service.build_file_from_stored_mapping",
|
||||
return_value=rebuilt_file,
|
||||
) as rebuild_file,
|
||||
):
|
||||
draft_vars = saver._build_variables_from_start_mapping({"sys.files": [raw_file]})
|
||||
|
||||
sys_var = draft_vars[0]
|
||||
assert sys_var.get_value().value[0] == rebuilt_file
|
||||
rebuild_file.assert_called_once_with(file_mapping=raw_file, tenant_id="tenant-1")
|
||||
|
||||
@pytest.fixture
|
||||
def mock_session(self):
|
||||
"""Mock SQLAlchemy session."""
|
||||
@ -218,6 +280,46 @@ class TestDraftVariableSaver:
|
||||
str(SystemVariableKey.WORKFLOW_EXECUTION_ID),
|
||||
}
|
||||
|
||||
@patch("services.workflow_draft_variable_service._batch_upsert_draft_variable", autospec=True)
|
||||
def test_start_node_save_normalizes_reserved_prefix_outputs(self, mock_batch_upsert):
|
||||
mock_session = MagicMock(spec=Session)
|
||||
mock_user = MagicMock(spec=Account)
|
||||
mock_user.id = "test-user-id"
|
||||
mock_user.tenant_id = "test-tenant-id"
|
||||
|
||||
saver = DraftVariableSaver(
|
||||
session=mock_session,
|
||||
app_id="test-app-id",
|
||||
node_id="start-node-id",
|
||||
node_type=BuiltinNodeTypes.START,
|
||||
node_execution_id="exec-id",
|
||||
user=mock_user,
|
||||
)
|
||||
|
||||
saver.save(
|
||||
outputs={
|
||||
"env.API_KEY": "secret",
|
||||
"conversation.session_id": "conversation-1",
|
||||
"sys.workflow_run_id": "run-id-123",
|
||||
}
|
||||
)
|
||||
|
||||
mock_batch_upsert.assert_called_once()
|
||||
draft_vars = mock_batch_upsert.call_args[0][1]
|
||||
|
||||
assert len(draft_vars) == 3
|
||||
|
||||
env_var = next(v for v in draft_vars if v.node_id == ENVIRONMENT_VARIABLE_NODE_ID)
|
||||
assert env_var.name == "API_KEY"
|
||||
assert env_var.editable is False
|
||||
|
||||
conversation_var = next(v for v in draft_vars if v.node_id == CONVERSATION_VARIABLE_NODE_ID)
|
||||
assert conversation_var.name == "session_id"
|
||||
assert conversation_var.node_execution_id is None
|
||||
|
||||
sys_var = next(v for v in draft_vars if v.node_id == SYSTEM_VARIABLE_NODE_ID)
|
||||
assert sys_var.name == str(SystemVariableKey.WORKFLOW_EXECUTION_ID)
|
||||
|
||||
|
||||
class TestWorkflowDraftVariableService:
|
||||
def _get_test_app_id(self):
|
||||
|
||||
@ -5,16 +5,16 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from dify_graph.entities.graph_config import NodeConfigDict, NodeConfigDictAdapter
|
||||
from dify_graph.enums import BuiltinNodeTypes
|
||||
from dify_graph.nodes.human_input.entities import (
|
||||
from core.workflow.human_input_compat import (
|
||||
EmailDeliveryConfig,
|
||||
EmailDeliveryMethod,
|
||||
EmailRecipients,
|
||||
ExternalRecipient,
|
||||
HumanInputNodeData,
|
||||
MemberRecipient,
|
||||
)
|
||||
from dify_graph.entities.graph_config import NodeConfigDict, NodeConfigDictAdapter
|
||||
from dify_graph.enums import BuiltinNodeTypes
|
||||
from dify_graph.nodes.human_input.entities import HumanInputNodeData
|
||||
from services import workflow_service as workflow_service_module
|
||||
from services.workflow_service import WorkflowService
|
||||
|
||||
@ -23,7 +23,7 @@ def _make_service() -> WorkflowService:
|
||||
return WorkflowService(session_maker=sessionmaker())
|
||||
|
||||
|
||||
def _build_node_config(delivery_methods: list[EmailDeliveryMethod]) -> NodeConfigDict:
|
||||
def _build_node_config(delivery_methods: list[EmailDeliveryMethod], *, legacy: bool = False) -> NodeConfigDict:
|
||||
node_data = HumanInputNodeData(
|
||||
title="Human Input",
|
||||
delivery_methods=delivery_methods,
|
||||
@ -31,6 +31,14 @@ def _build_node_config(delivery_methods: list[EmailDeliveryMethod]) -> NodeConfi
|
||||
inputs=[],
|
||||
user_actions=[],
|
||||
).model_dump(mode="json")
|
||||
if legacy:
|
||||
for delivery_method in node_data["delivery_methods"]:
|
||||
recipients = delivery_method.get("config", {}).get("recipients", {})
|
||||
if "include_bound_group" in recipients:
|
||||
recipients["whole_workspace"] = recipients.pop("include_bound_group")
|
||||
for recipient in recipients.get("items", []):
|
||||
if "reference_id" in recipient:
|
||||
recipient["user_id"] = recipient.pop("reference_id")
|
||||
node_data["type"] = BuiltinNodeTypes.HUMAN_INPUT
|
||||
return NodeConfigDictAdapter.validate_python({"id": "node-1", "data": node_data})
|
||||
|
||||
@ -41,7 +49,7 @@ def _make_email_method(enabled: bool = True, debug_mode: bool = False) -> EmailD
|
||||
enabled=enabled,
|
||||
config=EmailDeliveryConfig(
|
||||
recipients=EmailRecipients(
|
||||
whole_workspace=False,
|
||||
include_bound_group=False,
|
||||
items=[ExternalRecipient(email="tester@example.com")],
|
||||
),
|
||||
subject="Test subject",
|
||||
@ -69,7 +77,7 @@ def test_human_input_delivery_requires_draft_workflow():
|
||||
def test_human_input_delivery_allows_disabled_method(monkeypatch: pytest.MonkeyPatch):
|
||||
service = _make_service()
|
||||
delivery_method = _make_email_method(enabled=False)
|
||||
node_config = _build_node_config([delivery_method])
|
||||
node_config = _build_node_config([delivery_method], legacy=True)
|
||||
workflow = MagicMock()
|
||||
workflow.get_node_config_by_id.return_value = node_config
|
||||
service.get_draft_workflow = MagicMock(return_value=workflow) # type: ignore[method-assign]
|
||||
@ -105,7 +113,7 @@ def test_human_input_delivery_allows_disabled_method(monkeypatch: pytest.MonkeyP
|
||||
def test_human_input_delivery_dispatches_to_test_service(monkeypatch: pytest.MonkeyPatch):
|
||||
service = _make_service()
|
||||
delivery_method = _make_email_method(enabled=True)
|
||||
node_config = _build_node_config([delivery_method])
|
||||
node_config = _build_node_config([delivery_method], legacy=True)
|
||||
workflow = MagicMock()
|
||||
workflow.get_node_config_by_id.return_value = node_config
|
||||
service.get_draft_workflow = MagicMock(return_value=workflow) # type: ignore[method-assign]
|
||||
@ -144,7 +152,7 @@ def test_human_input_delivery_dispatches_to_test_service(monkeypatch: pytest.Mon
|
||||
def test_human_input_delivery_debug_mode_overrides_recipients(monkeypatch: pytest.MonkeyPatch):
|
||||
service = _make_service()
|
||||
delivery_method = _make_email_method(enabled=True, debug_mode=True)
|
||||
node_config = _build_node_config([delivery_method])
|
||||
node_config = _build_node_config([delivery_method], legacy=True)
|
||||
workflow = MagicMock()
|
||||
workflow.get_node_config_by_id.return_value = node_config
|
||||
service.get_draft_workflow = MagicMock(return_value=workflow) # type: ignore[method-assign]
|
||||
@ -178,8 +186,8 @@ def test_human_input_delivery_debug_mode_overrides_recipients(monkeypatch: pytes
|
||||
sent_method = test_service_instance.send_test.call_args.kwargs["method"]
|
||||
assert isinstance(sent_method, EmailDeliveryMethod)
|
||||
assert sent_method.config.debug_mode is True
|
||||
assert sent_method.config.recipients.whole_workspace is False
|
||||
assert sent_method.config.recipients.include_bound_group is False
|
||||
assert len(sent_method.config.recipients.items) == 1
|
||||
recipient = sent_method.config.recipients.items[0]
|
||||
assert isinstance(recipient, MemberRecipient)
|
||||
assert recipient.user_id == account.id
|
||||
assert recipient.reference_id == account.id
|
||||
|
||||
Reference in New Issue
Block a user