fix: resolve import migrations and test failures after segment 3 merge
- Migrate core.model_runtime -> dify_graph.model_runtime across 20+ files - Migrate core.workflow.file -> dify_graph.file across 15+ files - Migrate core.workflow.enums -> dify_graph.enums in service files - Fix SandboxContext phantom import in dify_graph/context/__init__.py - Fix core.app.workflow.node_factory -> core.workflow.node_factory - Fix toast import paths (useToastContext from toast/context) - Fix app-info.tsx import paths for relocated app-operations - Fix 15 frontend test files for API changes, missing QueryClientProvider, i18n key renames, and component behavior changes Made-with: Cursor
This commit is contained in:
parent
94b01f6821
commit
6b75188ddc
|
|
@ -1,4 +1,4 @@
|
|||
from core.workflow.file.helpers import get_signed_file_url_for_plugin
|
||||
from dify_graph.file.helpers import get_signed_file_url_for_plugin
|
||||
from flask import abort
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ from controllers.console.wraps import (
|
|||
only_edition_cloud,
|
||||
setup_required,
|
||||
)
|
||||
from core.workflow.file import helpers as file_helpers
|
||||
from dify_graph.file import helpers as file_helpers
|
||||
from extensions.ext_database import db
|
||||
from fields.member_fields import Account as AccountResponse
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import logging
|
||||
|
||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
|
||||
from flask import request
|
||||
from flask_restx import Resource, fields
|
||||
from pydantic import BaseModel
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from core.agent.entities import AgentEntity, AgentLog, AgentResult
|
|||
from core.agent.patterns.strategy_factory import StrategyFactory
|
||||
from core.app.apps.base_app_queue_manager import PublishFrom
|
||||
from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent
|
||||
from core.model_runtime.entities import (
|
||||
from dify_graph.model_runtime.entities import (
|
||||
AssistantPromptMessage,
|
||||
LLMResult,
|
||||
LLMResultChunk,
|
||||
|
|
@ -19,12 +19,12 @@ from core.model_runtime.entities import (
|
|||
TextPromptMessageContent,
|
||||
UserPromptMessage,
|
||||
)
|
||||
from core.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes
|
||||
from dify_graph.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes
|
||||
from core.prompt.agent_history_prompt_transform import AgentHistoryPromptTransform
|
||||
from core.tools.__base.tool import Tool
|
||||
from core.tools.entities.tool_entities import ToolInvokeMeta
|
||||
from core.tools.tool_engine import ToolEngine
|
||||
from core.workflow.file import file_manager
|
||||
from dify_graph.file import file_manager
|
||||
from models.model import Message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any
|
|||
|
||||
from core.agent.entities import AgentLog, AgentResult, ExecutionContext
|
||||
from core.model_manager import ModelInstance
|
||||
from core.model_runtime.entities import (
|
||||
from dify_graph.model_runtime.entities import (
|
||||
AssistantPromptMessage,
|
||||
LLMResult,
|
||||
LLMResultChunk,
|
||||
|
|
@ -19,10 +19,10 @@ from core.model_runtime.entities import (
|
|||
PromptMessage,
|
||||
PromptMessageTool,
|
||||
)
|
||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
||||
from core.model_runtime.entities.message_entities import TextPromptMessageContent
|
||||
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||
from dify_graph.model_runtime.entities.message_entities import TextPromptMessageContent
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage, ToolInvokeMeta
|
||||
from core.workflow.file import File
|
||||
from dify_graph.file import File
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.tools.__base.tool import Tool
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from collections.abc import Generator
|
|||
from typing import Any, Union
|
||||
|
||||
from core.agent.entities import AgentLog, AgentResult
|
||||
from core.model_runtime.entities import (
|
||||
from dify_graph.model_runtime.entities import (
|
||||
AssistantPromptMessage,
|
||||
LLMResult,
|
||||
LLMResultChunk,
|
||||
|
|
@ -23,7 +23,7 @@ from core.model_runtime.entities import (
|
|||
ToolPromptMessage,
|
||||
)
|
||||
from core.tools.entities.tool_entities import ToolInvokeMeta
|
||||
from core.workflow.file import File
|
||||
from dify_graph.file import File
|
||||
|
||||
from .base import AgentPattern
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any, Union
|
|||
from core.agent.entities import AgentLog, AgentResult, AgentScratchpadUnit, ExecutionContext
|
||||
from core.agent.output_parser.cot_output_parser import CotAgentOutputParser
|
||||
from core.model_manager import ModelInstance
|
||||
from core.model_runtime.entities import (
|
||||
from dify_graph.model_runtime.entities import (
|
||||
AssistantPromptMessage,
|
||||
LLMResult,
|
||||
LLMResultChunk,
|
||||
|
|
@ -17,7 +17,7 @@ from core.model_runtime.entities import (
|
|||
PromptMessage,
|
||||
SystemPromptMessage,
|
||||
)
|
||||
from core.workflow.file import File
|
||||
from dify_graph.file import File
|
||||
|
||||
from .base import AgentPattern, ToolInvokeHook
|
||||
|
||||
|
|
@ -204,7 +204,7 @@ class ReActStrategy(AgentPattern):
|
|||
tool_names = [tool.name for tool in prompt_tools]
|
||||
|
||||
# Format tools as JSON for comprehensive information
|
||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
|
||||
|
||||
tools_str = json.dumps(jsonable_encoder(prompt_tools), indent=2)
|
||||
tool_names_str = ", ".join(f'"{name}"' for name in tool_names)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from core.agent.entities import AgentEntity, ExecutionContext
|
||||
from core.model_manager import ModelInstance
|
||||
from core.model_runtime.entities.model_entities import ModelFeature
|
||||
from core.workflow.file.models import File
|
||||
from dify_graph.model_runtime.entities.model_entities import ModelFeature
|
||||
from dify_graph.file.models import File
|
||||
|
||||
from .base import AgentPattern, ToolInvokeHook
|
||||
from .function_call import FunctionCallStrategy
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import logging
|
||||
|
||||
from core.workflow.graph_engine.layers.base import GraphEngineLayer
|
||||
from core.workflow.graph_events.base import GraphEngineEvent
|
||||
from dify_graph.graph_engine.layers.base import GraphEngineLayer
|
||||
from dify_graph.graph_events.base import GraphEngineEvent
|
||||
|
||||
from core.sandbox import Sandbox
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from core.workflow.variables.types import SegmentType
|
||||
from dify_graph.variables.types import SegmentType
|
||||
|
||||
|
||||
class SuggestedQuestionsOutput(BaseModel):
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ This module provides utilities to:
|
|||
from collections.abc import Callable, Mapping, Sequence
|
||||
from typing import Any, cast
|
||||
|
||||
from core.workflow.file import File
|
||||
from core.workflow.variables.segments import ArrayFileSegment, FileSegment
|
||||
from dify_graph.file import File
|
||||
from dify_graph.variables.segments import ArrayFileSegment, FileSegment
|
||||
|
||||
FILE_PATH_FORMAT = "file-path"
|
||||
FILE_PATH_DESCRIPTION_SUFFIX = "this field contains a file path from the Dify sandbox"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""Utility functions for LLM generator."""
|
||||
|
||||
from core.model_runtime.entities.message_entities import (
|
||||
from dify_graph.model_runtime.entities.message_entities import (
|
||||
AssistantPromptMessage,
|
||||
PromptMessage,
|
||||
PromptMessageRole,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ This module defines the common protocol for memory implementations.
|
|||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Sequence
|
||||
|
||||
from core.model_runtime.entities import ImagePromptMessageContent, PromptMessage
|
||||
from dify_graph.model_runtime.entities import ImagePromptMessageContent, PromptMessage
|
||||
|
||||
|
||||
class BaseMemory(ABC):
|
||||
|
|
@ -49,7 +49,7 @@ class BaseMemory(ABC):
|
|||
:param message_limit: Maximum number of messages
|
||||
:return: Formatted history text
|
||||
"""
|
||||
from core.model_runtime.entities import (
|
||||
from dify_graph.model_runtime.entities import (
|
||||
PromptMessageRole,
|
||||
TextPromptMessageContent,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from sqlalchemy.orm import Session
|
|||
|
||||
from core.memory.base import BaseMemory
|
||||
from core.model_manager import ModelInstance
|
||||
from core.model_runtime.entities import (
|
||||
from dify_graph.model_runtime.entities import (
|
||||
AssistantPromptMessage,
|
||||
MultiModalPromptMessageContent,
|
||||
PromptMessage,
|
||||
|
|
@ -31,9 +31,9 @@ from core.model_runtime.entities import (
|
|||
ToolPromptMessage,
|
||||
UserPromptMessage,
|
||||
)
|
||||
from core.model_runtime.entities.message_entities import PromptMessageContentUnionTypes
|
||||
from dify_graph.model_runtime.entities.message_entities import PromptMessageContentUnionTypes
|
||||
from core.prompt.utils.extract_thread_messages import extract_thread_messages
|
||||
from core.workflow.file import file_manager
|
||||
from dify_graph.file import file_manager
|
||||
from extensions.ext_database import db
|
||||
from models.model import Message
|
||||
from models.workflow import WorkflowNodeExecutionModel
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any
|
|||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from dify_graph.model_runtime.utils.encoders import jsonable_encoder
|
||||
from core.session.cli_api import CliApiSession
|
||||
from core.skill.entities import ToolDependencies, ToolReference
|
||||
from core.tools.entities.tool_entities import ToolParameter, ToolProviderType
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from core.skill.entities.tool_dependencies import ToolDependencies
|
|||
from core.tools.signature import sign_tool_file
|
||||
from core.tools.tool_file_manager import ToolFileManager
|
||||
from core.virtual_environment.__base.helpers import pipeline
|
||||
from core.workflow.file import File, FileTransferMethod, FileType
|
||||
from dify_graph.file import File, FileTransferMethod, FileType
|
||||
|
||||
from ..bash.dify_cli import DifyCliConfig
|
||||
from ..entities import DifyCli
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any
|
|||
if TYPE_CHECKING: # pragma: no cover
|
||||
from models.model import File
|
||||
|
||||
from core.model_runtime.entities.message_entities import PromptMessageTool
|
||||
from dify_graph.model_runtime.entities.message_entities import PromptMessageTool
|
||||
|
||||
from core.tools.__base.tool_runtime import ToolRuntime
|
||||
from core.tools.entities.tool_entities import (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from core.workflow.nodes.base import BaseNodeData
|
||||
from dify_graph.nodes.base import BaseNodeData
|
||||
|
||||
|
||||
class CommandNodeData(BaseNodeData):
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@ import logging
|
|||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any
|
||||
|
||||
from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus
|
||||
from core.workflow.nodes.base.entities import VariableSelector
|
||||
from core.workflow.nodes.base.node import Node
|
||||
from core.workflow.nodes.base.variable_template_parser import VariableTemplateParser
|
||||
from dify_graph.enums import NodeType, WorkflowNodeExecutionStatus
|
||||
from dify_graph.nodes.base.entities import VariableSelector
|
||||
from dify_graph.nodes.base.node import Node
|
||||
from dify_graph.nodes.base.variable_template_parser import VariableTemplateParser
|
||||
|
||||
from core.sandbox import sandbox_debug
|
||||
from core.sandbox.bash.session import SANDBOX_READY_TIMEOUT
|
||||
from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError
|
||||
from core.virtual_environment.__base.helpers import submit_command, with_connection
|
||||
from core.workflow.node_events import NodeRunResult
|
||||
from core.workflow.nodes.base import variable_template_parser
|
||||
from dify_graph.node_events import NodeRunResult
|
||||
from dify_graph.nodes.base import variable_template_parser
|
||||
from core.workflow.nodes.command.entities import CommandNodeData
|
||||
from core.workflow.nodes.command.exc import CommandExecutionError
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from collections.abc import Sequence
|
||||
|
||||
from core.workflow.nodes.base import BaseNodeData
|
||||
from dify_graph.nodes.base import BaseNodeData
|
||||
|
||||
|
||||
class FileUploadNodeData(BaseNodeData):
|
||||
|
|
|
|||
|
|
@ -5,16 +5,16 @@ from collections.abc import Mapping, Sequence
|
|||
from pathlib import PurePosixPath
|
||||
from typing import Any, cast
|
||||
|
||||
from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus
|
||||
from core.workflow.nodes.base.node import Node
|
||||
from core.workflow.variables.segments import ArrayStringSegment, FileSegment
|
||||
from dify_graph.enums import NodeType, WorkflowNodeExecutionStatus
|
||||
from dify_graph.nodes.base.node import Node
|
||||
from dify_graph.variables.segments import ArrayStringSegment, FileSegment
|
||||
|
||||
from core.sandbox.bash.session import SANDBOX_READY_TIMEOUT
|
||||
from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError
|
||||
from core.virtual_environment.__base.helpers import pipeline
|
||||
from core.workflow.file import File, FileTransferMethod
|
||||
from core.workflow.node_events import NodeRunResult
|
||||
from core.workflow.variables import ArrayFileSegment
|
||||
from dify_graph.file import File, FileTransferMethod
|
||||
from dify_graph.node_events import NodeRunResult
|
||||
from dify_graph.variables import ArrayFileSegment
|
||||
from core.zip_sandbox import SandboxDownloadItem
|
||||
|
||||
from .entities import FileUploadNodeData
|
||||
|
|
|
|||
|
|
@ -17,15 +17,12 @@ from dify_graph.context.execution_context import (
|
|||
register_context_capturer,
|
||||
reset_context_provider,
|
||||
)
|
||||
from dify_graph.context.models import SandboxContext
|
||||
|
||||
__all__ = [
|
||||
"AppContext",
|
||||
"ContextProviderNotFoundError",
|
||||
"ExecutionContext",
|
||||
"IExecutionContext",
|
||||
"NullAppContext",
|
||||
"SandboxContext",
|
||||
"capture_current_context",
|
||||
"read_context",
|
||||
"register_context",
|
||||
|
|
|
|||
|
|
@ -517,7 +517,7 @@ class AgentNode(Node[AgentNodeData]):
|
|||
Fetch memory instance for saving node memory.
|
||||
This is a simplified version that doesn't require model_instance.
|
||||
"""
|
||||
from core.model_runtime.entities.model_entities import ModelType
|
||||
from dify_graph.model_runtime.entities.model_entities import ModelType
|
||||
|
||||
from core.model_manager import ModelManager
|
||||
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ class Node(Generic[NodeDataT]):
|
|||
Nested nodes are nodes with parent_node_id == self._node_id.
|
||||
They are executed before the main node to extract values from list[PromptMessage].
|
||||
"""
|
||||
from core.app.workflow.node_factory import DifyNodeFactory
|
||||
from core.workflow.node_factory import DifyNodeFactory
|
||||
|
||||
extractor_configs = self._find_extractor_node_configs()
|
||||
logger.debug("[NestedNode] Found %d nested nodes for parent '%s'", len(extractor_configs), self._node_id)
|
||||
|
|
@ -689,7 +689,7 @@ class Node(Generic[NodeDataT]):
|
|||
|
||||
@_dispatch.register
|
||||
def _(self, event: StreamChunkEvent) -> NodeRunStreamChunkEvent:
|
||||
from core.workflow.graph_events import ChunkType
|
||||
from dify_graph.graph_events import ChunkType
|
||||
|
||||
return NodeRunStreamChunkEvent(
|
||||
id=self.execution_id,
|
||||
|
|
@ -711,7 +711,7 @@ class Node(Generic[NodeDataT]):
|
|||
|
||||
@_dispatch.register
|
||||
def _(self, event: ToolCallChunkEvent) -> NodeRunStreamChunkEvent:
|
||||
from core.workflow.graph_events import ChunkType
|
||||
from dify_graph.graph_events import ChunkType
|
||||
|
||||
return NodeRunStreamChunkEvent(
|
||||
id=self._node_execution_id,
|
||||
|
|
@ -726,8 +726,8 @@ class Node(Generic[NodeDataT]):
|
|||
|
||||
@_dispatch.register
|
||||
def _(self, event: ToolResultChunkEvent) -> NodeRunStreamChunkEvent:
|
||||
from core.workflow.entities import ToolResult, ToolResultStatus
|
||||
from core.workflow.graph_events import ChunkType
|
||||
from dify_graph.entities import ToolResult, ToolResultStatus
|
||||
from dify_graph.graph_events import ChunkType
|
||||
|
||||
tool_result = event.tool_result or ToolResult()
|
||||
status: ToolResultStatus = tool_result.status or ToolResultStatus.SUCCESS
|
||||
|
|
@ -748,7 +748,7 @@ class Node(Generic[NodeDataT]):
|
|||
|
||||
@_dispatch.register
|
||||
def _(self, event: ThoughtChunkEvent) -> NodeRunStreamChunkEvent:
|
||||
from core.workflow.graph_events import ChunkType
|
||||
from dify_graph.graph_events import ChunkType
|
||||
|
||||
return NodeRunStreamChunkEvent(
|
||||
id=self._node_execution_id,
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ def _build_messages_from_trace(
|
|||
assistant_response: str,
|
||||
file_suffix: str = "",
|
||||
) -> list[PromptMessage]:
|
||||
from core.workflow.nodes.llm.entities import ModelTraceSegment, ToolTraceSegment
|
||||
from dify_graph.nodes.llm.entities import ModelTraceSegment, ToolTraceSegment
|
||||
|
||||
messages: list[PromptMessage] = []
|
||||
covered_text_len = 0
|
||||
|
|
@ -266,12 +266,12 @@ def _truncate_multimodal_content(message: PromptMessage) -> PromptMessage:
|
|||
|
||||
|
||||
def restore_multimodal_content_in_messages(messages: Sequence[PromptMessage]) -> list[PromptMessage]:
|
||||
from core.workflow.file import file_manager
|
||||
|
||||
return [_restore_message_content(msg, file_manager) for msg in messages]
|
||||
return [_restore_message_content(msg) for msg in messages]
|
||||
|
||||
|
||||
def _restore_message_content(message: PromptMessage, file_manager) -> PromptMessage:
|
||||
def _restore_message_content(message: PromptMessage) -> PromptMessage:
|
||||
from dify_graph.file.file_manager import restore_multimodal_content
|
||||
|
||||
content = message.content
|
||||
if content is None or isinstance(content, str):
|
||||
return message
|
||||
|
|
@ -279,7 +279,7 @@ def _restore_message_content(message: PromptMessage, file_manager) -> PromptMess
|
|||
restored_content: list[PromptMessageContentUnionTypes] = []
|
||||
for item in content:
|
||||
if isinstance(item, MultiModalPromptMessageContent):
|
||||
restored_item = file_manager.restore_multimodal_content(item)
|
||||
restored_item = restore_multimodal_content(item)
|
||||
restored_content.append(cast(PromptMessageContentUnionTypes, restored_item))
|
||||
else:
|
||||
restored_content.append(item)
|
||||
|
|
|
|||
|
|
@ -2201,7 +2201,7 @@ class LLMNode(Node[LLMNodeData]):
|
|||
|
||||
def _extract_prompt_files(self, variable_pool: VariablePool) -> list[File]:
|
||||
"""Extract files from prompt template variables."""
|
||||
from core.workflow.variables import ArrayFileVariable, FileVariable
|
||||
from dify_graph.variables import ArrayFileVariable, FileVariable
|
||||
|
||||
files: list[File] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -568,7 +568,7 @@ class ToolNode(Node[ToolNodeData]):
|
|||
:param parent_node_id: the parent node id to find nested nodes for
|
||||
:return: mapping of variable key to variable selector
|
||||
"""
|
||||
from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
|
||||
from dify_graph.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
|
||||
|
||||
result: dict[str, Sequence[str]] = {}
|
||||
nodes = graph_config.get("nodes", [])
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import Any
|
|||
|
||||
import orjson
|
||||
|
||||
from core.model_runtime.entities import PromptMessage
|
||||
from dify_graph.model_runtime.entities import PromptMessage
|
||||
|
||||
from .segment_group import SegmentGroup
|
||||
from .segments import ArrayFileSegment, ArrayPromptMessageSegment, FileSegment, Segment
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from collections.abc import Mapping
|
|||
from functools import reduce
|
||||
from typing import Any, cast
|
||||
|
||||
from core.workflow.enums import NodeType
|
||||
from dify_graph.enums import NodeType
|
||||
|
||||
from core.app.entities.app_asset_entities import AppAssetFileTree, AppAssetNode
|
||||
from core.sandbox.entities.config import AppAssets
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ extracting values from list[PromptMessage] variables.
|
|||
|
||||
from typing import Any
|
||||
|
||||
from core.workflow.enums import NodeType
|
||||
from dify_graph.enums import NodeType
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.model_runtime.entities import LLMMode
|
||||
from dify_graph.model_runtime.entities import LLMMode
|
||||
from services.model_provider_service import ModelProviderService
|
||||
from services.workflow.entities import NestedNodeGraphRequest, NestedNodeGraphResponse, NestedNodeParameterSchema
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from decimal import Decimal
|
|||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
||||
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||
|
||||
from core.agent.entities import AgentLog, ExecutionContext
|
||||
from core.agent.patterns.base import AgentPattern
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from decimal import Decimal
|
|||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
||||
from core.model_runtime.entities.message_entities import (
|
||||
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||
from dify_graph.model_runtime.entities.message_entities import (
|
||||
PromptMessageTool,
|
||||
SystemPromptMessage,
|
||||
UserPromptMessage,
|
||||
|
|
@ -312,7 +312,7 @@ class TestPromptMessageHandling:
|
|||
|
||||
def test_assistant_message_with_tool_calls(self, mock_model_instance, mock_context, mock_tool):
|
||||
"""Test that assistant messages can contain tool calls."""
|
||||
from core.model_runtime.entities.message_entities import AssistantPromptMessage
|
||||
from dify_graph.model_runtime.entities.message_entities import AssistantPromptMessage
|
||||
|
||||
tool_call = AssistantPromptMessage.ToolCall(
|
||||
id="call_123",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import pytest
|
|||
|
||||
from core.agent.entities import ExecutionContext
|
||||
from core.agent.patterns.react import ReActStrategy
|
||||
from core.model_runtime.entities import SystemPromptMessage, UserPromptMessage
|
||||
from dify_graph.model_runtime.entities import SystemPromptMessage, UserPromptMessage
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -33,7 +33,7 @@ def mock_context():
|
|||
@pytest.fixture
|
||||
def mock_tool():
|
||||
"""Create a mock tool."""
|
||||
from core.model_runtime.entities.message_entities import PromptMessageTool
|
||||
from dify_graph.model_runtime.entities.message_entities import PromptMessageTool
|
||||
|
||||
tool = MagicMock()
|
||||
tool.entity.identity.name = "test_tool"
|
||||
|
|
@ -158,7 +158,7 @@ class TestBuildPromptWithReactFormat:
|
|||
def test_scratchpad_appended_as_assistant_message(self, mock_model_instance, mock_context):
|
||||
"""Test that agent scratchpad is appended as AssistantPromptMessage."""
|
||||
from core.agent.entities import AgentScratchpadUnit
|
||||
from core.model_runtime.entities import AssistantPromptMessage
|
||||
from dify_graph.model_runtime.entities import AssistantPromptMessage
|
||||
|
||||
strategy = ReActStrategy(
|
||||
model_instance=mock_model_instance,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from core.model_runtime.entities.model_entities import ModelFeature
|
||||
from dify_graph.model_runtime.entities.model_entities import ModelFeature
|
||||
|
||||
from core.agent.entities import AgentEntity, ExecutionContext
|
||||
from core.agent.patterns.function_call import FunctionCallStrategy
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ from decimal import Decimal
|
|||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
||||
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||
|
||||
from core.agent.entities import AgentEntity, AgentLog, AgentPromptEntity, AgentResult
|
||||
from core.model_runtime.entities import SystemPromptMessage, UserPromptMessage
|
||||
from dify_graph.model_runtime.entities import SystemPromptMessage, UserPromptMessage
|
||||
|
||||
|
||||
class TestOrganizePromptMessages:
|
||||
|
|
@ -184,7 +184,7 @@ class TestClearUserPromptImageMessages:
|
|||
|
||||
def test_original_messages_not_modified(self, mock_runner):
|
||||
"""Test that original messages are not modified (deep copy)."""
|
||||
from core.model_runtime.entities.message_entities import (
|
||||
from dify_graph.model_runtime.entities.message_entities import (
|
||||
ImagePromptMessageContent,
|
||||
TextPromptMessageContent,
|
||||
)
|
||||
|
|
@ -365,13 +365,13 @@ class TestOrganizeUserQuery:
|
|||
|
||||
def test_query_with_files(self, mock_runner):
|
||||
"""Test organizing a query with files."""
|
||||
from core.workflow.file.models import File
|
||||
from dify_graph.file.models import File
|
||||
|
||||
mock_file = MagicMock(spec=File)
|
||||
mock_runner.files = [mock_file]
|
||||
|
||||
with patch("core.agent.agent_app_runner.file_manager") as mock_fm:
|
||||
from core.model_runtime.entities.message_entities import ImagePromptMessageContent
|
||||
from dify_graph.model_runtime.entities.message_entities import ImagePromptMessageContent
|
||||
|
||||
mock_fm.to_prompt_message_content.return_value = ImagePromptMessageContent(
|
||||
data="http://example.com/image.jpg",
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from unittest.mock import MagicMock
|
|||
|
||||
from core.app.apps.base_app_queue_manager import PublishFrom
|
||||
from core.app.apps.workflow_app_runner import WorkflowBasedAppRunner
|
||||
from core.workflow.graph_events import NodeRunStreamChunkEvent
|
||||
from core.workflow.nodes import NodeType
|
||||
from dify_graph.graph_events import NodeRunStreamChunkEvent
|
||||
from dify_graph.enums import NodeType
|
||||
|
||||
|
||||
class DummyQueueManager:
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
from unittest.mock import patch
|
||||
|
||||
from core.model_runtime.entities.message_entities import ImagePromptMessageContent
|
||||
from core.workflow.file.file_manager import (
|
||||
from dify_graph.model_runtime.entities.message_entities import ImagePromptMessageContent
|
||||
from dify_graph.file.file_manager import (
|
||||
_encode_file_ref,
|
||||
restore_multimodal_content,
|
||||
to_prompt_message_content,
|
||||
)
|
||||
|
||||
from core.workflow.file import File, FileTransferMethod, FileType
|
||||
from dify_graph.file import File, FileTransferMethod, FileType
|
||||
|
||||
|
||||
class TestEncodeFileRef:
|
||||
|
|
@ -52,8 +52,8 @@ class TestEncodeFileRef:
|
|||
class TestToPromptMessageContent:
|
||||
"""Tests for to_prompt_message_content function with file_ref field."""
|
||||
|
||||
@patch("core.workflow.file.file_manager.dify_config")
|
||||
@patch("core.workflow.file.file_manager._get_encoded_string")
|
||||
@patch("dify_graph.file.file_manager.dify_config")
|
||||
@patch("dify_graph.file.file_manager._get_encoded_string")
|
||||
def test_includes_file_ref(self, mock_get_encoded, mock_config):
|
||||
"""Generated content should include file_ref field."""
|
||||
mock_config.MULTIMODAL_SEND_FORMAT = "base64"
|
||||
|
|
@ -121,9 +121,9 @@ class TestRestoreMultimodalContent:
|
|||
|
||||
assert result.url == "https://example.com/image.png"
|
||||
|
||||
@patch("core.workflow.file.file_manager.dify_config")
|
||||
@patch("core.workflow.file.file_manager._build_file_from_ref")
|
||||
@patch("core.workflow.file.file_manager._to_url")
|
||||
@patch("dify_graph.file.file_manager.dify_config")
|
||||
@patch("dify_graph.file.file_manager._build_file_from_ref")
|
||||
@patch("dify_graph.file.file_manager._to_url")
|
||||
def test_restores_url_from_file_ref(self, mock_to_url, mock_build_file, mock_config):
|
||||
"""Content should be restored from file_ref when url is empty (url mode)."""
|
||||
mock_config.MULTIMODAL_SEND_FORMAT = "url"
|
||||
|
|
@ -144,9 +144,9 @@ class TestRestoreMultimodalContent:
|
|||
assert result.url == "https://restored-url.com/image.png"
|
||||
mock_build_file.assert_called_once()
|
||||
|
||||
@patch("core.workflow.file.file_manager.dify_config")
|
||||
@patch("core.workflow.file.file_manager._build_file_from_ref")
|
||||
@patch("core.workflow.file.file_manager._get_encoded_string")
|
||||
@patch("dify_graph.file.file_manager.dify_config")
|
||||
@patch("dify_graph.file.file_manager._build_file_from_ref")
|
||||
@patch("dify_graph.file.file_manager._get_encoded_string")
|
||||
def test_restores_base64_from_file_ref(self, mock_get_encoded, mock_build_file, mock_config):
|
||||
"""Content should be restored as base64 when in base64 mode."""
|
||||
mock_config.MULTIMODAL_SEND_FORMAT = "base64"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Unit tests for sandbox file path detection and conversion.
|
|||
"""
|
||||
|
||||
import pytest
|
||||
from core.workflow.variables.segments import ArrayFileSegment, FileSegment
|
||||
from dify_graph.variables.segments import ArrayFileSegment, FileSegment
|
||||
|
||||
from core.llm_generator.output_parser.file_ref import (
|
||||
FILE_PATH_DESCRIPTION_SUFFIX,
|
||||
|
|
@ -13,7 +13,7 @@ from core.llm_generator.output_parser.file_ref import (
|
|||
detect_file_path_fields,
|
||||
is_file_path_property,
|
||||
)
|
||||
from core.workflow.file import File, FileTransferMethod, FileType
|
||||
from dify_graph.file import File, FileTransferMethod, FileType
|
||||
|
||||
|
||||
def _build_file(file_id: str) -> File:
|
||||
|
|
|
|||
|
|
@ -2,21 +2,21 @@
|
|||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from core.workflow.entities.tool_entities import ToolResultStatus
|
||||
from core.workflow.enums import NodeType
|
||||
from core.workflow.graph.graph import Graph
|
||||
from core.workflow.graph_engine.response_coordinator.coordinator import ResponseStreamCoordinator
|
||||
from core.workflow.graph_engine.response_coordinator.session import ResponseSession
|
||||
from core.workflow.nodes.base.entities import BaseNodeData
|
||||
from core.workflow.nodes.base.template import Template, VariableSegment
|
||||
from dify_graph.entities.tool_entities import ToolResultStatus
|
||||
from dify_graph.enums import NodeType
|
||||
from dify_graph.graph.graph import Graph
|
||||
from dify_graph.graph_engine.response_coordinator.coordinator import ResponseStreamCoordinator
|
||||
from dify_graph.graph_engine.response_coordinator.session import ResponseSession
|
||||
from dify_graph.nodes.base.entities import BaseNodeData
|
||||
from dify_graph.nodes.base.template import Template, VariableSegment
|
||||
|
||||
from core.workflow.graph_events import (
|
||||
from dify_graph.graph_events import (
|
||||
ChunkType,
|
||||
NodeRunStreamChunkEvent,
|
||||
ToolCall,
|
||||
ToolResult,
|
||||
)
|
||||
from core.workflow.runtime import VariablePool
|
||||
from dify_graph.runtime import VariablePool
|
||||
|
||||
|
||||
class TestResponseCoordinatorObjectStreaming:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Tests for StreamChunkEvent and its subclasses."""
|
||||
|
||||
from core.workflow.entities import ToolCall, ToolResult, ToolResultStatus
|
||||
from core.workflow.node_events import (
|
||||
from dify_graph.entities import ToolCall, ToolResult, ToolResultStatus
|
||||
from dify_graph.node_events import (
|
||||
ChunkType,
|
||||
StreamChunkEvent,
|
||||
ThoughtChunkEvent,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from io import BytesIO
|
|||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from core.workflow.enums import WorkflowNodeExecutionStatus
|
||||
from core.workflow.system_variable import SystemVariable
|
||||
from dify_graph.enums import WorkflowNodeExecutionStatus
|
||||
from dify_graph.system_variable import SystemVariable
|
||||
|
||||
from core.entities.provider_entities import BasicProviderConfig
|
||||
from core.virtual_environment.__base.entities import (
|
||||
|
|
@ -19,9 +19,10 @@ from core.virtual_environment.__base.entities import (
|
|||
from core.virtual_environment.__base.virtual_environment import VirtualEnvironment
|
||||
from core.virtual_environment.channel.queue_transport import QueueTransportReadCloser
|
||||
from core.virtual_environment.channel.transport import NopTransportWriteCloser
|
||||
from core.workflow.entities import GraphInitParams
|
||||
from dify_graph.entities import GraphInitParams
|
||||
from dify_graph.entities.graph_init_params import DIFY_RUN_CONTEXT_KEY
|
||||
from core.workflow.nodes.command.node import CommandNode
|
||||
from core.workflow.runtime import GraphRuntimeState, VariablePool
|
||||
from dify_graph.runtime import GraphRuntimeState, VariablePool
|
||||
|
||||
|
||||
class FakeVirtualEnvironment(VirtualEnvironment):
|
||||
|
|
@ -138,14 +139,18 @@ def _make_node(
|
|||
variable_pool = VariablePool(system_variables=system_variables, user_inputs={})
|
||||
runtime_state = GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter())
|
||||
init_params = GraphInitParams(
|
||||
tenant_id="t",
|
||||
app_id="a",
|
||||
workflow_id="w",
|
||||
graph_config={},
|
||||
user_id="u",
|
||||
user_from="account",
|
||||
invoke_from="debugger",
|
||||
call_depth=0,
|
||||
run_context={
|
||||
DIFY_RUN_CONTEXT_KEY: {
|
||||
"tenant_id": "t",
|
||||
"app_id": "a",
|
||||
"user_id": "u",
|
||||
"user_from": "account",
|
||||
"invoke_from": "debugger",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if vm is not None:
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
import string
|
||||
from unittest.mock import patch
|
||||
|
||||
from core.model_runtime.entities.message_entities import (
|
||||
from dify_graph.model_runtime.entities.message_entities import (
|
||||
ImagePromptMessageContent,
|
||||
TextPromptMessageContent,
|
||||
UserPromptMessage,
|
||||
)
|
||||
from core.workflow.nodes.llm.llm_utils import (
|
||||
from dify_graph.nodes.llm.llm_utils import (
|
||||
_truncate_multimodal_content,
|
||||
build_context,
|
||||
restore_multimodal_content_in_messages,
|
||||
|
|
@ -100,7 +100,7 @@ class TestBuildContext:
|
|||
|
||||
def test_excludes_system_messages(self):
|
||||
"""System messages should be excluded from context."""
|
||||
from core.model_runtime.entities.message_entities import SystemPromptMessage
|
||||
from dify_graph.model_runtime.entities.message_entities import SystemPromptMessage
|
||||
|
||||
messages = [
|
||||
SystemPromptMessage(content="You are a helpful assistant."),
|
||||
|
|
@ -125,12 +125,12 @@ class TestBuildContext:
|
|||
|
||||
def test_builds_context_with_tool_calls_from_generation_data(self):
|
||||
"""Should reconstruct full conversation including tool calls when generation_data is provided."""
|
||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
||||
from core.model_runtime.entities.message_entities import (
|
||||
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||
from dify_graph.model_runtime.entities.message_entities import (
|
||||
AssistantPromptMessage,
|
||||
ToolPromptMessage,
|
||||
)
|
||||
from core.workflow.nodes.llm.entities import (
|
||||
from dify_graph.nodes.llm.entities import (
|
||||
LLMGenerationData,
|
||||
LLMTraceSegment,
|
||||
ModelTraceSegment,
|
||||
|
|
@ -199,12 +199,12 @@ class TestBuildContext:
|
|||
|
||||
def test_builds_context_with_multiple_tool_calls(self):
|
||||
"""Should handle multiple tool calls in a single conversation."""
|
||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
||||
from core.model_runtime.entities.message_entities import (
|
||||
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||
from dify_graph.model_runtime.entities.message_entities import (
|
||||
AssistantPromptMessage,
|
||||
ToolPromptMessage,
|
||||
)
|
||||
from core.workflow.nodes.llm.entities import (
|
||||
from dify_graph.nodes.llm.entities import (
|
||||
LLMGenerationData,
|
||||
LLMTraceSegment,
|
||||
ModelTraceSegment,
|
||||
|
|
@ -291,8 +291,8 @@ class TestBuildContext:
|
|||
|
||||
def test_builds_context_with_empty_trace(self):
|
||||
"""Should fallback to simple context when trace is empty."""
|
||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
||||
from core.workflow.nodes.llm.entities import LLMGenerationData
|
||||
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||
from dify_graph.nodes.llm.entities import LLMGenerationData
|
||||
|
||||
messages = [UserPromptMessage(content="Hello!")]
|
||||
|
||||
|
|
@ -318,7 +318,7 @@ class TestBuildContext:
|
|||
class TestRestoreMultimodalContentInMessages:
|
||||
"""Tests for restore_multimodal_content_in_messages function."""
|
||||
|
||||
@patch("core.workflow.file.file_manager.restore_multimodal_content")
|
||||
@patch("dify_graph.file.file_manager.restore_multimodal_content")
|
||||
def test_restores_multimodal_content(self, mock_restore):
|
||||
"""Should restore multimodal content in messages."""
|
||||
# Setup mock
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ from collections.abc import Generator
|
|||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
||||
from core.workflow.entities.tool_entities import ToolResultStatus
|
||||
from core.workflow.nodes.llm.node import LLMNode
|
||||
from dify_graph.model_runtime.entities.llm_entities import LLMUsage
|
||||
from dify_graph.entities.tool_entities import ToolResultStatus
|
||||
from dify_graph.nodes.llm.node import LLMNode
|
||||
|
||||
from core.workflow.entities import ToolCallResult
|
||||
from core.workflow.node_events import ModelInvokeCompletedEvent, NodeEventBase
|
||||
from dify_graph.entities import ToolCallResult
|
||||
from dify_graph.node_events import ModelInvokeCompletedEvent, NodeEventBase
|
||||
|
||||
|
||||
class _StubModelInstance:
|
||||
|
|
@ -109,9 +109,9 @@ def test_stream_llm_events_no_reasoning_results_in_empty_sequence():
|
|||
|
||||
|
||||
def test_serialize_tool_call_strips_files_to_ids():
|
||||
file_cls = pytest.importorskip("core.workflow.file").File
|
||||
file_type = pytest.importorskip("core.workflow.file.enums").FileType
|
||||
transfer_method = pytest.importorskip("core.workflow.file.enums").FileTransferMethod
|
||||
file_cls = pytest.importorskip("dify_graph.file").File
|
||||
file_type = pytest.importorskip("dify_graph.file.enums").FileType
|
||||
transfer_method = pytest.importorskip("dify_graph.file.enums").FileTransferMethod
|
||||
|
||||
file_with_id = file_cls(
|
||||
id="f1",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
import type { AppListResponse } from '@/models/app'
|
||||
import type { App } from '@/types/app'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import List from '@/app/components/apps/list'
|
||||
|
|
@ -165,9 +166,15 @@ const createPage = (apps: App[], hasMore = false, page = 1): AppListResponse =>
|
|||
total: apps.length,
|
||||
})
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
})
|
||||
|
||||
const renderList = (searchParams?: Record<string, string>) => {
|
||||
return renderWithNuqs(
|
||||
<List controlRefreshList={0} />,
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<List controlRefreshList={0} />
|
||||
</QueryClientProvider>,
|
||||
{ searchParams },
|
||||
)
|
||||
}
|
||||
|
|
@ -213,7 +220,9 @@ describe('App List Browsing Flow', () => {
|
|||
|
||||
it('should transition from loading to content when data loads', () => {
|
||||
mockIsLoading = true
|
||||
const { rerender } = renderWithNuqs(<List controlRefreshList={0} />)
|
||||
const { rerender } = renderWithNuqs(
|
||||
<QueryClientProvider client={queryClient}><List controlRefreshList={0} /></QueryClientProvider>,
|
||||
)
|
||||
|
||||
const skeletonCards = document.querySelectorAll('.animate-pulse')
|
||||
expect(skeletonCards.length).toBeGreaterThan(0)
|
||||
|
|
@ -224,7 +233,9 @@ describe('App List Browsing Flow', () => {
|
|||
createMockApp({ id: 'app-1', name: 'Loaded App' }),
|
||||
])]
|
||||
|
||||
rerender(<List controlRefreshList={0} />)
|
||||
rerender(
|
||||
<QueryClientProvider client={queryClient}><List controlRefreshList={0} /></QueryClientProvider>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('Loaded App')).toBeInTheDocument()
|
||||
})
|
||||
|
|
@ -420,9 +431,13 @@ describe('App List Browsing Flow', () => {
|
|||
it('should call refetch when controlRefreshList increments', () => {
|
||||
mockPages = [createPage([createMockApp()])]
|
||||
|
||||
const { rerender } = renderWithNuqs(<List controlRefreshList={0} />)
|
||||
const { rerender } = renderWithNuqs(
|
||||
<QueryClientProvider client={queryClient}><List controlRefreshList={0} /></QueryClientProvider>,
|
||||
)
|
||||
|
||||
rerender(<List controlRefreshList={1} />)
|
||||
rerender(
|
||||
<QueryClientProvider client={queryClient}><List controlRefreshList={1} /></QueryClientProvider>,
|
||||
)
|
||||
|
||||
expect(mockRefetch).toHaveBeenCalled()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
import type { AppListResponse } from '@/models/app'
|
||||
import type { App } from '@/types/app'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import List from '@/app/components/apps/list'
|
||||
|
|
@ -218,8 +219,16 @@ const createPage = (apps: App[]): AppListResponse => ({
|
|||
total: apps.length,
|
||||
})
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
})
|
||||
|
||||
const renderList = () => {
|
||||
return renderWithNuqs(<List controlRefreshList={0} />)
|
||||
return renderWithNuqs(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<List controlRefreshList={0} />
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
describe('Create App Flow', () => {
|
||||
|
|
@ -245,7 +254,7 @@ describe('Create App Flow', () => {
|
|||
expect(screen.getByText('app.createApp')).toBeInTheDocument()
|
||||
expect(screen.getByText('app.newApp.startFromBlank')).toBeInTheDocument()
|
||||
expect(screen.getByText('app.newApp.startFromTemplate')).toBeInTheDocument()
|
||||
expect(screen.getByText('app.importDSL')).toBeInTheDocument()
|
||||
expect(screen.getByText('app.importApp')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render NewAppCard when user is not an editor', () => {
|
||||
|
|
@ -354,7 +363,7 @@ describe('Create App Flow', () => {
|
|||
it('should open DSL import modal when "Import DSL" is clicked', async () => {
|
||||
renderList()
|
||||
|
||||
fireEvent.click(screen.getByText('app.importDSL'))
|
||||
fireEvent.click(screen.getByText('app.importApp'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('create-from-dsl-modal')).toBeInTheDocument()
|
||||
|
|
@ -364,7 +373,7 @@ describe('Create App Flow', () => {
|
|||
it('should close DSL import modal on cancel', async () => {
|
||||
renderList()
|
||||
|
||||
fireEvent.click(screen.getByText('app.importDSL'))
|
||||
fireEvent.click(screen.getByText('app.importApp'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('create-from-dsl-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
|
@ -378,7 +387,7 @@ describe('Create App Flow', () => {
|
|||
it('should call onPlanInfoChanged and refetch on successful DSL import', async () => {
|
||||
renderList()
|
||||
|
||||
fireEvent.click(screen.getByText('app.importDSL'))
|
||||
fireEvent.click(screen.getByText('app.importApp'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('create-from-dsl-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
|
@ -451,7 +460,7 @@ describe('Create App Flow', () => {
|
|||
// Rapidly click different create options
|
||||
fireEvent.click(screen.getByText('app.newApp.startFromBlank'))
|
||||
fireEvent.click(screen.getByText('app.newApp.startFromTemplate'))
|
||||
fireEvent.click(screen.getByText('app.importDSL'))
|
||||
fireEvent.click(screen.getByText('app.importApp'))
|
||||
|
||||
// Should not crash, and some modal should be present
|
||||
await waitFor(() => {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,19 @@ import { render, screen } from '@testing-library/react'
|
|||
import userEvent from '@testing-library/user-event'
|
||||
import * as React from 'react'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import AppInfo from '..'
|
||||
import AppInfo from '../index'
|
||||
|
||||
let mockIsCurrentWorkspaceEditor = true
|
||||
const mockSetPanelOpen = vi.fn()
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({ replace: vi.fn() }),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-apps', () => ({
|
||||
useInvalidateAppList: () => vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor,
|
||||
|
|
|
|||
|
|
@ -263,11 +263,10 @@ describe('AppCard', () => {
|
|||
})
|
||||
|
||||
it('should render app icon', () => {
|
||||
// AppIcon component renders the emoji icon from app data
|
||||
const { container } = render(<AppCard app={mockApp} />)
|
||||
// Check that the icon container is rendered (AppIcon renders within the card)
|
||||
const iconElement = container.querySelector('[class*="icon"]') || container.querySelector('img')
|
||||
expect(iconElement || screen.getByText(mockApp.icon)).toBeTruthy()
|
||||
const emojiElement = container.querySelector('em-emoji')
|
||||
expect(emojiElement).toBeTruthy()
|
||||
expect(emojiElement?.getAttribute('id')).toBe(mockApp.icon)
|
||||
})
|
||||
|
||||
it('should render app type icon', () => {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ vi.mock('@/app/education-apply/hooks', () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({ replace: vi.fn() }),
|
||||
useSearchParams: () => new URLSearchParams(),
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/use-import-dsl', () => ({
|
||||
useImportDSL: () => ({
|
||||
handleImportDSL: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { act, fireEvent, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
|
||||
|
|
@ -200,9 +201,17 @@ beforeAll(() => {
|
|||
} as unknown as typeof IntersectionObserver
|
||||
})
|
||||
|
||||
// Render helper wrapping with shared nuqs testing helper.
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
})
|
||||
|
||||
const renderList = (searchParams = '') => {
|
||||
return renderWithNuqs(<List />, { searchParams })
|
||||
return renderWithNuqs(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<List />
|
||||
</QueryClientProvider>,
|
||||
{ searchParams },
|
||||
)
|
||||
}
|
||||
|
||||
describe('List', () => {
|
||||
|
|
@ -399,10 +408,14 @@ describe('List', () => {
|
|||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle multiple renders without issues', () => {
|
||||
const { rerender } = renderWithNuqs(<List />)
|
||||
const { rerender } = renderWithNuqs(
|
||||
<QueryClientProvider client={queryClient}><List /></QueryClientProvider>,
|
||||
)
|
||||
expect(screen.getByText('app.types.all')).toBeInTheDocument()
|
||||
|
||||
rerender(<List />)
|
||||
rerender(
|
||||
<QueryClientProvider client={queryClient}><List /></QueryClientProvider>,
|
||||
)
|
||||
expect(screen.getByText('app.types.all')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ describe('CreateAppCard', () => {
|
|||
|
||||
expect(screen.getByText('app.newApp.startFromBlank')).toBeInTheDocument()
|
||||
expect(screen.getByText('app.newApp.startFromTemplate')).toBeInTheDocument()
|
||||
expect(screen.getByText('app.importDSL')).toBeInTheDocument()
|
||||
expect(screen.getByText('app.importApp')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render all buttons as clickable', () => {
|
||||
|
|
@ -190,7 +190,7 @@ describe('CreateAppCard', () => {
|
|||
it('should open DSL modal when clicking Import DSL', () => {
|
||||
render(<CreateAppCard ref={defaultRef} />)
|
||||
|
||||
fireEvent.click(screen.getByText('app.importDSL'))
|
||||
fireEvent.click(screen.getByText('app.importApp'))
|
||||
|
||||
expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
|
@ -198,7 +198,7 @@ describe('CreateAppCard', () => {
|
|||
it('should close DSL modal when clicking close button', () => {
|
||||
render(<CreateAppCard ref={defaultRef} />)
|
||||
|
||||
fireEvent.click(screen.getByText('app.importDSL'))
|
||||
fireEvent.click(screen.getByText('app.importApp'))
|
||||
expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByTestId('close-dsl-modal'))
|
||||
|
|
@ -209,7 +209,7 @@ describe('CreateAppCard', () => {
|
|||
const mockOnSuccess = vi.fn()
|
||||
render(<CreateAppCard ref={defaultRef} onSuccess={mockOnSuccess} />)
|
||||
|
||||
fireEvent.click(screen.getByText('app.importDSL'))
|
||||
fireEvent.click(screen.getByText('app.importApp'))
|
||||
fireEvent.click(screen.getByTestId('success-dsl-modal'))
|
||||
|
||||
expect(mockOnPlanInfoChanged).toHaveBeenCalled()
|
||||
|
|
@ -245,7 +245,7 @@ describe('CreateAppCard', () => {
|
|||
fireEvent.click(screen.getByText('app.newApp.startFromTemplate'))
|
||||
fireEvent.click(screen.getByTestId('close-template-dialog'))
|
||||
|
||||
fireEvent.click(screen.getByText('app.importDSL'))
|
||||
fireEvent.click(screen.getByText('app.importApp'))
|
||||
fireEvent.click(screen.getByTestId('close-dsl-modal'))
|
||||
|
||||
expect(screen.queryByTestId('create-app-modal')).not.toBeInTheDocument()
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const getPromptEditor = () => {
|
|||
vi.mock('@/utils/var', () => ({
|
||||
checkKeys: (_keys: string[]) => ({ isValid: true }),
|
||||
getNewVar: (key: string, type: string) => ({ key, name: key, type, required: true }),
|
||||
basePath: '',
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/app/configuration/config-prompt/confirm-add-var', () => ({
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ vi.mock('lexical', async (importOriginal) => {
|
|||
getChildren: () => mocks.rootLines.map(line => ({
|
||||
getTextContent: () => line,
|
||||
})),
|
||||
getAllTextNodes: () => [],
|
||||
}),
|
||||
TextNode: class TextNode {
|
||||
__text: string
|
||||
|
|
|
|||
|
|
@ -77,12 +77,16 @@ const { mockConfig, mockEnv } = vi.hoisted(() => ({
|
|||
},
|
||||
},
|
||||
}))
|
||||
vi.mock('@/config', () => ({
|
||||
get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION },
|
||||
get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY },
|
||||
IS_DEV: false,
|
||||
IS_CE_EDITION: false,
|
||||
}))
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
return {
|
||||
...actual,
|
||||
get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION },
|
||||
get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY },
|
||||
IS_DEV: false,
|
||||
IS_CE_EDITION: false,
|
||||
}
|
||||
})
|
||||
vi.mock('@/env', () => mockEnv)
|
||||
|
||||
const baseAppContextValue: AppContextValue = {
|
||||
|
|
|
|||
|
|
@ -55,8 +55,20 @@ vi.mock('@/service/workflow', () => ({
|
|||
syncWorkflowDraft: (p: unknown) => mockSyncWorkflowDraft(p),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/fetch', () => ({ postWithKeepalive: vi.fn() }))
|
||||
vi.mock('@/config', () => ({ API_PREFIX: '/api' }))
|
||||
vi.mock('@/service/fetch', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/fetch')>()
|
||||
return {
|
||||
...actual,
|
||||
postWithKeepalive: vi.fn(),
|
||||
}
|
||||
})
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
return {
|
||||
...actual,
|
||||
API_PREFIX: '/api',
|
||||
}
|
||||
})
|
||||
|
||||
const mockHandleRefreshWorkflowDraft = vi.fn()
|
||||
vi.mock('@/app/components/workflow-app/hooks', () => ({
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ vi.mock('@/app/components/workflow/store', () => ({
|
|||
getState: () => ({
|
||||
appId: 'app-1',
|
||||
isWorkflowDataLoaded: true,
|
||||
debouncedSyncWorkflowDraft: undefined,
|
||||
debouncedSyncWorkflowDraft: { cancel: vi.fn() },
|
||||
setSyncWorkflowDraftHash: mockSetSyncWorkflowDraftHash,
|
||||
setIsSyncingWorkflowDraft: vi.fn(),
|
||||
setEnvironmentVariables: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ vi.mock('../use-workflow', () => ({
|
|||
|
||||
vi.mock('../../utils', () => ({
|
||||
getNodesConnectedSourceOrTargetHandleIdsMap: vi.fn(() => ({})),
|
||||
genNodeMetaData: vi.fn(({ type, sort }: { type: string, sort: number }) => ({ type, sort })),
|
||||
}))
|
||||
|
||||
// useNodesSyncDraft is used REAL — via renderWorkflowHook + hooksStoreProps
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ describe('useWorkflowTextChunk', () => {
|
|||
},
|
||||
})
|
||||
|
||||
result.current.handleWorkflowTextChunk({ data: { text: ' World' } } as TextChunkResponse)
|
||||
result.current.handleWorkflowTextChunk({ data: { text: ' World', chunk_type: 'text' } } as TextChunkResponse)
|
||||
|
||||
const state = store.getState().workflowRunningData!
|
||||
expect(state.resultText).toBe('Hello World')
|
||||
|
|
|
|||
|
|
@ -172,9 +172,9 @@ describe('createWorkflowStore', () => {
|
|||
expect(store.getState().controlMode).toBe('pointer')
|
||||
})
|
||||
|
||||
it('should default controlMode to hand when localStorage has no value', () => {
|
||||
it('should default controlMode to pointer when localStorage has no value', () => {
|
||||
const store = createStore()
|
||||
expect(store.getState().controlMode).toBe('hand')
|
||||
expect(store.getState().controlMode).toBe('pointer')
|
||||
})
|
||||
|
||||
it('should read panelWidth from localStorage', () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue