mirror of
https://github.com/langgenius/dify.git
synced 2026-05-02 08:28:03 +08:00
feat(sandbox): refactor sandbox file handling to include app_id
- Updated API routes to use app_id instead of sandbox_id for file operations, aligning with user-specific sandbox workspaces. - Enhanced SandboxFileService and related classes to accommodate app_id in file listing and download functionalities. - Refactored storage key generation for sandbox archives to include app_id, ensuring proper file organization. - Adjusted frontend contracts and services to reflect the new app_id parameter in API calls.
This commit is contained in:
@ -2,10 +2,9 @@ import time
|
||||
from collections.abc import Mapping
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from core.sandbox import SandboxManager
|
||||
from core.entities.provider_entities import BasicProviderConfig
|
||||
from core.virtual_environment.__base.entities import (
|
||||
Arch,
|
||||
CommandStatus,
|
||||
@ -24,7 +23,9 @@ from core.workflow.runtime import GraphRuntimeState, VariablePool
|
||||
from core.workflow.system_variable import SystemVariable
|
||||
|
||||
|
||||
class FakeSandbox(VirtualEnvironment):
|
||||
class FakeVirtualEnvironment(VirtualEnvironment):
|
||||
"""Fake VirtualEnvironment for testing CommandNode execution."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@ -98,17 +99,39 @@ class FakeSandbox(VirtualEnvironment):
|
||||
def validate(cls, options: Mapping[str, Any]) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_config_schema(cls) -> list[BasicProviderConfig]:
|
||||
return []
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_sandbox_manager():
|
||||
SandboxManager.clear()
|
||||
yield
|
||||
SandboxManager.clear()
|
||||
|
||||
def _make_mock_sandbox(vm: VirtualEnvironment) -> MagicMock:
|
||||
"""Create a mock Sandbox wrapping a VirtualEnvironment for testing."""
|
||||
sandbox = MagicMock()
|
||||
sandbox.vm = vm
|
||||
sandbox.tenant_id = "test-tenant"
|
||||
sandbox.app_id = "test-app"
|
||||
sandbox.user_id = "test-user"
|
||||
sandbox.assets_id = "test-assets"
|
||||
sandbox.wait_ready = MagicMock() # No-op for tests
|
||||
return sandbox
|
||||
|
||||
|
||||
def _make_node(
|
||||
*, command: str, working_directory: str = "", workflow_execution_id: str = "test-workflow-exec-id"
|
||||
*,
|
||||
command: str,
|
||||
working_directory: str = "",
|
||||
workflow_execution_id: str = "test-workflow-exec-id",
|
||||
vm: FakeVirtualEnvironment | None = None,
|
||||
) -> CommandNode:
|
||||
"""Create a CommandNode for testing.
|
||||
|
||||
Args:
|
||||
command: The shell command to execute.
|
||||
working_directory: Optional working directory for command execution.
|
||||
workflow_execution_id: Identifier for the workflow execution.
|
||||
vm: Optional FakeVirtualEnvironment. If provided, a mock Sandbox
|
||||
wrapping this VM will be set on the runtime state.
|
||||
"""
|
||||
system_variables = SystemVariable(workflow_execution_id=workflow_execution_id)
|
||||
variable_pool = VariablePool(system_variables=system_variables, user_inputs={})
|
||||
runtime_state = GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter())
|
||||
@ -123,6 +146,10 @@ def _make_node(
|
||||
call_depth=0,
|
||||
)
|
||||
|
||||
if vm is not None:
|
||||
sandbox = _make_mock_sandbox(vm)
|
||||
runtime_state.set_sandbox(sandbox)
|
||||
|
||||
return CommandNode(
|
||||
id="node-instance",
|
||||
config={
|
||||
@ -139,17 +166,14 @@ def _make_node(
|
||||
|
||||
|
||||
def test_command_node_success_executes_in_sandbox():
|
||||
workflow_execution_id = "test-exec-success"
|
||||
vm = FakeVirtualEnvironment(stdout=b"ok\n", stderr=b"")
|
||||
node = _make_node(
|
||||
command="echo {{#pre_node_id.number#}}",
|
||||
working_directory="dir-{{#pre_node_id.number#}}",
|
||||
workflow_execution_id=workflow_execution_id,
|
||||
vm=vm,
|
||||
)
|
||||
node.graph_runtime_state.variable_pool.add(("pre_node_id", "number"), 42)
|
||||
|
||||
sandbox = FakeSandbox(stdout=b"ok\n", stderr=b"")
|
||||
SandboxManager.register(workflow_execution_id, sandbox)
|
||||
|
||||
result = node._run() # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
@ -157,20 +181,19 @@ def test_command_node_success_executes_in_sandbox():
|
||||
assert result.outputs["stderr"] == ""
|
||||
assert result.outputs["exit_code"] == 0
|
||||
|
||||
assert sandbox.last_execute_command is not None
|
||||
assert sandbox.last_execute_command == ["echo", "42"]
|
||||
assert sandbox.last_execute_cwd == "dir-42"
|
||||
assert vm.last_execute_command is not None
|
||||
# CommandNode wraps commands in bash -c
|
||||
assert vm.last_execute_command == ["bash", "-c", "echo 42"]
|
||||
assert vm.last_execute_cwd == "dir-42"
|
||||
|
||||
|
||||
def test_command_node_nonzero_exit_code_returns_failed_result():
|
||||
workflow_execution_id = "test-exec-nonzero"
|
||||
node = _make_node(command="false", workflow_execution_id=workflow_execution_id)
|
||||
sandbox = FakeSandbox(
|
||||
vm = FakeVirtualEnvironment(
|
||||
stdout=b"out",
|
||||
stderr=b"err",
|
||||
statuses=[CommandStatus(status=CommandStatus.Status.COMPLETED, exit_code=2)],
|
||||
)
|
||||
SandboxManager.register(workflow_execution_id, sandbox)
|
||||
node = _make_node(command="false", vm=vm)
|
||||
|
||||
result = node._run() # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
@ -184,15 +207,13 @@ def test_command_node_timeout_returns_failed_result_and_closes_transports(monkey
|
||||
|
||||
monkeypatch.setattr(command_node_module, "COMMAND_NODE_TIMEOUT_SECONDS", 1)
|
||||
|
||||
workflow_execution_id = "test-exec-timeout"
|
||||
node = _make_node(command="sleep 10", workflow_execution_id=workflow_execution_id)
|
||||
sandbox = FakeSandbox(
|
||||
vm = FakeVirtualEnvironment(
|
||||
stdout=b"",
|
||||
stderr=b"",
|
||||
statuses=[CommandStatus(status=CommandStatus.Status.RUNNING, exit_code=None)] * 1000,
|
||||
close_streams=False,
|
||||
)
|
||||
SandboxManager.register(workflow_execution_id, sandbox)
|
||||
node = _make_node(command="sleep 10", vm=vm)
|
||||
|
||||
result = node._run() # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
@ -202,8 +223,7 @@ def test_command_node_timeout_returns_failed_result_and_closes_transports(monkey
|
||||
|
||||
|
||||
def test_command_node_no_sandbox_returns_failed():
|
||||
workflow_execution_id = "test-exec-no-sandbox"
|
||||
node = _make_node(command="echo hello", workflow_execution_id=workflow_execution_id)
|
||||
node = _make_node(command="echo hello")
|
||||
|
||||
result = node._run() # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user