mirror of
https://github.com/langgenius/dify.git
synced 2026-01-08 07:14:14 +00:00
Compare commits
5 Commits
refactor/u
...
feat/limit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
737d290fdc | ||
|
|
81d9a9d00d | ||
|
|
215e318600 | ||
|
|
53faf90bea | ||
|
|
d2f12037da |
@@ -1,3 +1,6 @@
|
|||||||
|
from libs.exception import BaseHTTPException
|
||||||
|
|
||||||
|
|
||||||
class WorkflowInUseError(ValueError):
|
class WorkflowInUseError(ValueError):
|
||||||
"""Raised when attempting to delete a workflow that's in use by an app"""
|
"""Raised when attempting to delete a workflow that's in use by an app"""
|
||||||
|
|
||||||
@@ -8,3 +11,17 @@ class DraftWorkflowDeletionError(ValueError):
|
|||||||
"""Raised when attempting to delete a draft workflow"""
|
"""Raised when attempting to delete a draft workflow"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationVariableDescriptionTooLongError(BaseHTTPException):
|
||||||
|
"""Raised when conversation variable description exceeds maximum length"""
|
||||||
|
|
||||||
|
error_code = "conversation_variable_description_too_long"
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, current_length: int, max_length: int):
|
||||||
|
description = (
|
||||||
|
f"Conversation variable description exceeds maximum length of "
|
||||||
|
f"{max_length} characters. Current length: {current_length} characters."
|
||||||
|
)
|
||||||
|
super().__init__(description)
|
||||||
|
|||||||
@@ -42,8 +42,16 @@ from services.enterprise.plugin_manager_service import PluginCredentialType
|
|||||||
from services.errors.app import IsDraftWorkflowError, WorkflowHashNotEqualError
|
from services.errors.app import IsDraftWorkflowError, WorkflowHashNotEqualError
|
||||||
from services.workflow.workflow_converter import WorkflowConverter
|
from services.workflow.workflow_converter import WorkflowConverter
|
||||||
|
|
||||||
from .errors.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError
|
from .errors.workflow_service import (
|
||||||
from .workflow_draft_variable_service import DraftVariableSaver, DraftVarLoader, WorkflowDraftVariableService
|
ConversationVariableDescriptionTooLongError,
|
||||||
|
DraftWorkflowDeletionError,
|
||||||
|
WorkflowInUseError,
|
||||||
|
)
|
||||||
|
from .workflow_draft_variable_service import (
|
||||||
|
DraftVariableSaver,
|
||||||
|
DraftVarLoader,
|
||||||
|
WorkflowDraftVariableService,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowService:
|
class WorkflowService:
|
||||||
@@ -189,6 +197,42 @@ class WorkflowService:
|
|||||||
|
|
||||||
return workflows, has_more
|
return workflows, has_more
|
||||||
|
|
||||||
|
def _validate_conversation_variable_descriptions(
|
||||||
|
self,
|
||||||
|
conversation_variables: Sequence[Variable],
|
||||||
|
existing_workflow: Workflow | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Validate conversation variable descriptions for length constraints.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
conversation_variables: List of conversation variables to validate
|
||||||
|
existing_workflow: Existing workflow for backward compatibility check (optional)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ConversationVariableDescriptionTooLongError: If any description exceeds the length limit and shouldn't be allowed
|
||||||
|
"""
|
||||||
|
MAX_CONVERSATION_VARIABLE_DESCRIPTION_LENGTH = 256
|
||||||
|
|
||||||
|
# Check if existing workflow has any conversation variables with long descriptions
|
||||||
|
has_existing_long_descriptions = False
|
||||||
|
if existing_workflow and existing_workflow.conversation_variables:
|
||||||
|
for var in existing_workflow.conversation_variables:
|
||||||
|
if len(var.description) > MAX_CONVERSATION_VARIABLE_DESCRIPTION_LENGTH:
|
||||||
|
has_existing_long_descriptions = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# Validate new conversation variables
|
||||||
|
for variable in conversation_variables:
|
||||||
|
description_length = len(variable.description)
|
||||||
|
if description_length > MAX_CONVERSATION_VARIABLE_DESCRIPTION_LENGTH:
|
||||||
|
# Allow existing workflows with long descriptions to be updated
|
||||||
|
# But don't allow new long descriptions or extending existing ones
|
||||||
|
if not has_existing_long_descriptions:
|
||||||
|
raise ConversationVariableDescriptionTooLongError(
|
||||||
|
current_length=description_length, max_length=MAX_CONVERSATION_VARIABLE_DESCRIPTION_LENGTH
|
||||||
|
)
|
||||||
|
|
||||||
def sync_draft_workflow(
|
def sync_draft_workflow(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -213,6 +257,12 @@ class WorkflowService:
|
|||||||
# validate features structure
|
# validate features structure
|
||||||
self.validate_features_structure(app_model=app_model, features=features)
|
self.validate_features_structure(app_model=app_model, features=features)
|
||||||
|
|
||||||
|
# validate conversation variable descriptions
|
||||||
|
self._validate_conversation_variable_descriptions(
|
||||||
|
conversation_variables=conversation_variables,
|
||||||
|
existing_workflow=workflow,
|
||||||
|
)
|
||||||
|
|
||||||
# create draft workflow if not found
|
# create draft workflow if not found
|
||||||
if not workflow:
|
if not workflow:
|
||||||
workflow = Workflow(
|
workflow = Workflow(
|
||||||
|
|||||||
@@ -1,26 +1,40 @@
|
|||||||
from unittest.mock import MagicMock
|
import json
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from core.variables.variables import StringVariable
|
||||||
|
from models.account import Account
|
||||||
from models.model import App
|
from models.model import App
|
||||||
from models.workflow import Workflow
|
from models.workflow import Workflow
|
||||||
|
from services.errors.workflow_service import ConversationVariableDescriptionTooLongError
|
||||||
from services.workflow_service import WorkflowService
|
from services.workflow_service import WorkflowService
|
||||||
|
|
||||||
|
|
||||||
class TestWorkflowService:
|
class TestWorkflowService:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def workflow_service(self):
|
def workflow_service(self):
|
||||||
mock_session_maker = MagicMock()
|
# Mock the database dependencies to avoid Flask app context issues
|
||||||
return WorkflowService(mock_session_maker)
|
with patch("services.workflow_service.db") as mock_db:
|
||||||
|
with patch("services.workflow_service.sessionmaker") as mock_sessionmaker:
|
||||||
|
mock_db.engine = MagicMock()
|
||||||
|
mock_sessionmaker.return_value = MagicMock()
|
||||||
|
return WorkflowService()
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_app(self):
|
def mock_app(self):
|
||||||
app = MagicMock(spec=App)
|
app = MagicMock(spec=App)
|
||||||
app.id = "app-id-1"
|
app.id = "app-id-1"
|
||||||
app.workflow_id = "workflow-id-1"
|
|
||||||
app.tenant_id = "tenant-id-1"
|
app.tenant_id = "tenant-id-1"
|
||||||
|
app.mode = "workflow"
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_account(self):
|
||||||
|
account = MagicMock(spec=Account)
|
||||||
|
account.id = "user-id-1"
|
||||||
|
return account
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_workflows(self):
|
def mock_workflows(self):
|
||||||
workflows = []
|
workflows = []
|
||||||
@@ -47,6 +61,7 @@ class TestWorkflowService:
|
|||||||
mock_session.scalars.assert_not_called()
|
mock_session.scalars.assert_not_called()
|
||||||
|
|
||||||
def test_get_all_published_workflow_basic(self, workflow_service, mock_app, mock_workflows):
|
def test_get_all_published_workflow_basic(self, workflow_service, mock_app, mock_workflows):
|
||||||
|
mock_app.workflow_id = "workflow-id-1"
|
||||||
mock_session = MagicMock()
|
mock_session = MagicMock()
|
||||||
mock_scalar_result = MagicMock()
|
mock_scalar_result = MagicMock()
|
||||||
mock_scalar_result.all.return_value = mock_workflows[:3]
|
mock_scalar_result.all.return_value = mock_workflows[:3]
|
||||||
@@ -61,6 +76,7 @@ class TestWorkflowService:
|
|||||||
mock_session.scalars.assert_called_once()
|
mock_session.scalars.assert_called_once()
|
||||||
|
|
||||||
def test_get_all_published_workflow_pagination(self, workflow_service, mock_app, mock_workflows):
|
def test_get_all_published_workflow_pagination(self, workflow_service, mock_app, mock_workflows):
|
||||||
|
mock_app.workflow_id = "workflow-id-1"
|
||||||
mock_session = MagicMock()
|
mock_session = MagicMock()
|
||||||
mock_scalar_result = MagicMock()
|
mock_scalar_result = MagicMock()
|
||||||
# Return 4 items when limit is 3, which should indicate has_more=True
|
# Return 4 items when limit is 3, which should indicate has_more=True
|
||||||
@@ -88,6 +104,7 @@ class TestWorkflowService:
|
|||||||
assert has_more is False
|
assert has_more is False
|
||||||
|
|
||||||
def test_get_all_published_workflow_user_filter(self, workflow_service, mock_app, mock_workflows):
|
def test_get_all_published_workflow_user_filter(self, workflow_service, mock_app, mock_workflows):
|
||||||
|
mock_app.workflow_id = "workflow-id-1"
|
||||||
mock_session = MagicMock()
|
mock_session = MagicMock()
|
||||||
mock_scalar_result = MagicMock()
|
mock_scalar_result = MagicMock()
|
||||||
# Filter workflows for user-id-1
|
# Filter workflows for user-id-1
|
||||||
@@ -108,6 +125,7 @@ class TestWorkflowService:
|
|||||||
assert "created_by" in str(args)
|
assert "created_by" in str(args)
|
||||||
|
|
||||||
def test_get_all_published_workflow_named_only(self, workflow_service, mock_app, mock_workflows):
|
def test_get_all_published_workflow_named_only(self, workflow_service, mock_app, mock_workflows):
|
||||||
|
mock_app.workflow_id = "workflow-id-1"
|
||||||
mock_session = MagicMock()
|
mock_session = MagicMock()
|
||||||
mock_scalar_result = MagicMock()
|
mock_scalar_result = MagicMock()
|
||||||
# Filter workflows that have a marked_name
|
# Filter workflows that have a marked_name
|
||||||
@@ -128,6 +146,7 @@ class TestWorkflowService:
|
|||||||
assert "marked_name !=" in str(args)
|
assert "marked_name !=" in str(args)
|
||||||
|
|
||||||
def test_get_all_published_workflow_combined_filters(self, workflow_service, mock_app, mock_workflows):
|
def test_get_all_published_workflow_combined_filters(self, workflow_service, mock_app, mock_workflows):
|
||||||
|
mock_app.workflow_id = "workflow-id-1"
|
||||||
mock_session = MagicMock()
|
mock_session = MagicMock()
|
||||||
mock_scalar_result = MagicMock()
|
mock_scalar_result = MagicMock()
|
||||||
# Combined filter: user-id-1 and has marked_name
|
# Combined filter: user-id-1 and has marked_name
|
||||||
@@ -149,6 +168,7 @@ class TestWorkflowService:
|
|||||||
assert "marked_name !=" in str(args)
|
assert "marked_name !=" in str(args)
|
||||||
|
|
||||||
def test_get_all_published_workflow_empty_result(self, workflow_service, mock_app):
|
def test_get_all_published_workflow_empty_result(self, workflow_service, mock_app):
|
||||||
|
mock_app.workflow_id = "workflow-id-1"
|
||||||
mock_session = MagicMock()
|
mock_session = MagicMock()
|
||||||
mock_scalar_result = MagicMock()
|
mock_scalar_result = MagicMock()
|
||||||
mock_scalar_result.all.return_value = []
|
mock_scalar_result.all.return_value = []
|
||||||
@@ -161,3 +181,267 @@ class TestWorkflowService:
|
|||||||
assert workflows == []
|
assert workflows == []
|
||||||
assert has_more is False
|
assert has_more is False
|
||||||
mock_session.scalars.assert_called_once()
|
mock_session.scalars.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
class TestSyncDraftWorkflow:
|
||||||
|
"""Test cases focused on the sync_draft_workflow method."""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def workflow_service(self):
|
||||||
|
# Mock the database dependencies to avoid Flask app context issues
|
||||||
|
with patch("services.workflow_service.db") as mock_db:
|
||||||
|
with patch("services.workflow_service.sessionmaker") as mock_sessionmaker:
|
||||||
|
mock_db.engine = MagicMock()
|
||||||
|
mock_sessionmaker.return_value = MagicMock()
|
||||||
|
return WorkflowService()
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_app(self):
|
||||||
|
app = MagicMock(spec=App)
|
||||||
|
app.id = "app-id-1"
|
||||||
|
app.tenant_id = "tenant-id-1"
|
||||||
|
app.mode = "workflow"
|
||||||
|
return app
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_account(self):
|
||||||
|
account = MagicMock(spec=Account)
|
||||||
|
account.id = "user-id-1"
|
||||||
|
return account
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_graph(self):
|
||||||
|
return {"nodes": [], "edges": []}
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_features(self):
|
||||||
|
return {"retriever_resource": {"enabled": True}}
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_environment_variables(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_conversation_variables(self):
|
||||||
|
return [
|
||||||
|
StringVariable(
|
||||||
|
id="var-1", name="test_var", description="A valid description within limits", value="test_value"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
@patch("services.workflow_service.db.session")
|
||||||
|
@patch("services.workflow_service.app_draft_workflow_was_synced")
|
||||||
|
def test_sync_draft_workflow_creates_new_workflow(
|
||||||
|
self,
|
||||||
|
mock_signal,
|
||||||
|
mock_db_session,
|
||||||
|
workflow_service,
|
||||||
|
mock_app,
|
||||||
|
mock_account,
|
||||||
|
sample_graph,
|
||||||
|
sample_features,
|
||||||
|
sample_environment_variables,
|
||||||
|
sample_conversation_variables,
|
||||||
|
):
|
||||||
|
"""Test that sync_draft_workflow creates a new workflow when none exists."""
|
||||||
|
# Mock get_draft_workflow to return None (no existing workflow)
|
||||||
|
with patch.object(workflow_service, "get_draft_workflow", return_value=None):
|
||||||
|
with patch.object(workflow_service, "validate_features_structure"):
|
||||||
|
result = workflow_service.sync_draft_workflow(
|
||||||
|
app_model=mock_app,
|
||||||
|
graph=sample_graph,
|
||||||
|
features=sample_features,
|
||||||
|
unique_hash="test-hash",
|
||||||
|
account=mock_account,
|
||||||
|
environment_variables=sample_environment_variables,
|
||||||
|
conversation_variables=sample_conversation_variables,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify a new workflow was created
|
||||||
|
assert result is not None
|
||||||
|
mock_db_session.add.assert_called_once()
|
||||||
|
mock_db_session.commit.assert_called_once()
|
||||||
|
mock_signal.send.assert_called_once()
|
||||||
|
|
||||||
|
@patch("services.workflow_service.db.session")
|
||||||
|
@patch("services.workflow_service.app_draft_workflow_was_synced")
|
||||||
|
def test_sync_draft_workflow_updates_existing_workflow(
|
||||||
|
self,
|
||||||
|
mock_signal,
|
||||||
|
mock_db_session,
|
||||||
|
workflow_service,
|
||||||
|
mock_app,
|
||||||
|
mock_account,
|
||||||
|
sample_graph,
|
||||||
|
sample_features,
|
||||||
|
sample_environment_variables,
|
||||||
|
sample_conversation_variables,
|
||||||
|
):
|
||||||
|
"""Test that sync_draft_workflow updates an existing workflow."""
|
||||||
|
# Mock existing workflow
|
||||||
|
existing_workflow = MagicMock(spec=Workflow)
|
||||||
|
existing_workflow.unique_hash = "test-hash"
|
||||||
|
existing_workflow.conversation_variables = []
|
||||||
|
|
||||||
|
with patch.object(workflow_service, "get_draft_workflow", return_value=existing_workflow):
|
||||||
|
with patch.object(workflow_service, "validate_features_structure"):
|
||||||
|
result = workflow_service.sync_draft_workflow(
|
||||||
|
app_model=mock_app,
|
||||||
|
graph=sample_graph,
|
||||||
|
features=sample_features,
|
||||||
|
unique_hash="test-hash",
|
||||||
|
account=mock_account,
|
||||||
|
environment_variables=sample_environment_variables,
|
||||||
|
conversation_variables=sample_conversation_variables,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify existing workflow was updated
|
||||||
|
assert result == existing_workflow
|
||||||
|
assert existing_workflow.graph == json.dumps(sample_graph)
|
||||||
|
assert existing_workflow.features == json.dumps(sample_features)
|
||||||
|
assert existing_workflow.updated_by == mock_account.id
|
||||||
|
assert existing_workflow.environment_variables == sample_environment_variables
|
||||||
|
assert existing_workflow.conversation_variables == sample_conversation_variables
|
||||||
|
mock_db_session.add.assert_not_called() # Should not add new workflow
|
||||||
|
mock_db_session.commit.assert_called_once()
|
||||||
|
mock_signal.send.assert_called_once()
|
||||||
|
|
||||||
|
def test_sync_draft_workflow_validates_conversation_variable_descriptions(
|
||||||
|
self, workflow_service, mock_app, mock_account, sample_graph, sample_features, sample_environment_variables
|
||||||
|
):
|
||||||
|
"""Test that sync_draft_workflow validates conversation variable descriptions."""
|
||||||
|
# Create conversation variable with description exceeding limit
|
||||||
|
long_description = "a" * 300 # Exceeds 256 character limit
|
||||||
|
long_desc_variables = [
|
||||||
|
StringVariable(id="var-1", name="test_var", description=long_description, value="test_value")
|
||||||
|
]
|
||||||
|
|
||||||
|
with patch.object(workflow_service, "get_draft_workflow", return_value=None):
|
||||||
|
with patch.object(workflow_service, "validate_features_structure"):
|
||||||
|
with pytest.raises(ConversationVariableDescriptionTooLongError) as exc_info:
|
||||||
|
workflow_service.sync_draft_workflow(
|
||||||
|
app_model=mock_app,
|
||||||
|
graph=sample_graph,
|
||||||
|
features=sample_features,
|
||||||
|
unique_hash="test-hash",
|
||||||
|
account=mock_account,
|
||||||
|
environment_variables=sample_environment_variables,
|
||||||
|
conversation_variables=long_desc_variables,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "exceeds maximum length of 256 characters" in str(exc_info.value)
|
||||||
|
assert "Current length: 300 characters" in str(exc_info.value)
|
||||||
|
|
||||||
|
def test_sync_draft_workflow_allows_existing_long_descriptions(
|
||||||
|
self, workflow_service, mock_app, mock_account, sample_graph, sample_features, sample_environment_variables
|
||||||
|
):
|
||||||
|
"""Test that sync_draft_workflow allows updates when existing workflow has long descriptions."""
|
||||||
|
# Create existing workflow with long description
|
||||||
|
long_description = "a" * 300
|
||||||
|
existing_variable = StringVariable(
|
||||||
|
id="existing-var", name="existing_var", description=long_description, value="existing_value"
|
||||||
|
)
|
||||||
|
existing_workflow = MagicMock(spec=Workflow)
|
||||||
|
existing_workflow.unique_hash = "test-hash"
|
||||||
|
existing_workflow.conversation_variables = [existing_variable]
|
||||||
|
|
||||||
|
# New variables with long descriptions should be allowed
|
||||||
|
new_long_desc_variables = [
|
||||||
|
StringVariable(id="new-var", name="new_var", description=long_description, value="new_value")
|
||||||
|
]
|
||||||
|
|
||||||
|
with patch.object(workflow_service, "get_draft_workflow", return_value=existing_workflow):
|
||||||
|
with patch.object(workflow_service, "validate_features_structure"):
|
||||||
|
with patch("services.workflow_service.db.session"):
|
||||||
|
with patch("services.workflow_service.app_draft_workflow_was_synced"):
|
||||||
|
# Should not raise exception
|
||||||
|
result = workflow_service.sync_draft_workflow(
|
||||||
|
app_model=mock_app,
|
||||||
|
graph=sample_graph,
|
||||||
|
features=sample_features,
|
||||||
|
unique_hash="test-hash",
|
||||||
|
account=mock_account,
|
||||||
|
environment_variables=sample_environment_variables,
|
||||||
|
conversation_variables=new_long_desc_variables,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == existing_workflow
|
||||||
|
|
||||||
|
def test_sync_draft_workflow_handles_empty_conversation_variables(
|
||||||
|
self, workflow_service, mock_app, mock_account, sample_graph, sample_features, sample_environment_variables
|
||||||
|
):
|
||||||
|
"""Test that empty conversation variables list is handled correctly."""
|
||||||
|
with patch.object(workflow_service, "get_draft_workflow", return_value=None):
|
||||||
|
with patch.object(workflow_service, "validate_features_structure"):
|
||||||
|
with patch("services.workflow_service.db.session"):
|
||||||
|
with patch("services.workflow_service.app_draft_workflow_was_synced"):
|
||||||
|
result = workflow_service.sync_draft_workflow(
|
||||||
|
app_model=mock_app,
|
||||||
|
graph=sample_graph,
|
||||||
|
features=sample_features,
|
||||||
|
unique_hash="test-hash",
|
||||||
|
account=mock_account,
|
||||||
|
environment_variables=sample_environment_variables,
|
||||||
|
conversation_variables=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
|
||||||
|
def test_sync_draft_workflow_handles_multiple_variables_one_invalid(
|
||||||
|
self, workflow_service, mock_app, mock_account, sample_graph, sample_features, sample_environment_variables
|
||||||
|
):
|
||||||
|
"""Test that validation fails when one of multiple variables exceeds limit."""
|
||||||
|
mixed_variables = [
|
||||||
|
StringVariable(id="var-1", name="valid_var", description="Valid description", value="valid_value"),
|
||||||
|
StringVariable(
|
||||||
|
id="var-2",
|
||||||
|
name="invalid_var",
|
||||||
|
description="a" * 300, # Exceeds limit
|
||||||
|
value="invalid_value",
|
||||||
|
),
|
||||||
|
StringVariable(
|
||||||
|
id="var-3",
|
||||||
|
name="another_valid_var",
|
||||||
|
description="Another valid description",
|
||||||
|
value="another_valid_value",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
with patch.object(workflow_service, "get_draft_workflow", return_value=None):
|
||||||
|
with patch.object(workflow_service, "validate_features_structure"):
|
||||||
|
with pytest.raises(ConversationVariableDescriptionTooLongError) as exc_info:
|
||||||
|
workflow_service.sync_draft_workflow(
|
||||||
|
app_model=mock_app,
|
||||||
|
graph=sample_graph,
|
||||||
|
features=sample_features,
|
||||||
|
unique_hash="test-hash",
|
||||||
|
account=mock_account,
|
||||||
|
environment_variables=sample_environment_variables,
|
||||||
|
conversation_variables=mixed_variables,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "exceeds maximum length of 256 characters" in str(exc_info.value)
|
||||||
|
assert "Current length: 300 characters" in str(exc_info.value)
|
||||||
|
|
||||||
|
def test_sync_draft_workflow_handles_empty_descriptions(
|
||||||
|
self, workflow_service, mock_app, mock_account, sample_graph, sample_features, sample_environment_variables
|
||||||
|
):
|
||||||
|
"""Test that empty description strings are handled correctly."""
|
||||||
|
empty_desc_variables = [StringVariable(id="var-1", name="empty_desc_var", description="", value="test_value")]
|
||||||
|
|
||||||
|
with patch.object(workflow_service, "get_draft_workflow", return_value=None):
|
||||||
|
with patch.object(workflow_service, "validate_features_structure"):
|
||||||
|
with patch("services.workflow_service.db.session"):
|
||||||
|
with patch("services.workflow_service.app_draft_workflow_was_synced"):
|
||||||
|
# Should not raise exception
|
||||||
|
result = workflow_service.sync_draft_workflow(
|
||||||
|
app_model=mock_app,
|
||||||
|
graph=sample_graph,
|
||||||
|
features=sample_features,
|
||||||
|
unique_hash="test-hash",
|
||||||
|
account=mock_account,
|
||||||
|
environment_variables=sample_environment_variables,
|
||||||
|
conversation_variables=empty_desc_variables,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
|||||||
@@ -340,7 +340,17 @@ export const useWorkflowRun = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
featuresStore?.setState({ features: mappedFeatures })
|
featuresStore?.setState({ features: mappedFeatures })
|
||||||
workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || [])
|
// slice description of env & chatVar
|
||||||
|
const newEnvList = (publishedWorkflow.environment_variables || []).map(env => ({
|
||||||
|
...env,
|
||||||
|
description: env.description.slice(0, 256),
|
||||||
|
}))
|
||||||
|
const newChatVarList = (publishedWorkflow.conversation_variables || []).map(chatVar => ({
|
||||||
|
...chatVar,
|
||||||
|
description: chatVar.description.slice(0, 256),
|
||||||
|
}))
|
||||||
|
workflowStore.getState().setEnvironmentVariables(newEnvList || [])
|
||||||
|
workflowStore.getState().setConversationVariables(newChatVarList || [])
|
||||||
}, [featuresStore, handleUpdateWorkflowCanvas, workflowStore])
|
}, [featuresStore, handleUpdateWorkflowCanvas, workflowStore])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -235,11 +235,13 @@ const ChatVariableModal = ({
|
|||||||
return
|
return
|
||||||
if (!chatVar && varList.some(chatVar => chatVar.name === name))
|
if (!chatVar && varList.some(chatVar => chatVar.name === name))
|
||||||
return notify({ type: 'error', message: 'name is existed' })
|
return notify({ type: 'error', message: 'name is existed' })
|
||||||
// if (type !== ChatVarType.Object && !value)
|
|
||||||
// return notify({ type: 'error', message: 'value can not be empty' })
|
|
||||||
if (type === ChatVarType.Object && objectValue.some(item => !item.key && !!item.value))
|
if (type === ChatVarType.Object && objectValue.some(item => !item.key && !!item.value))
|
||||||
return notify({ type: 'error', message: 'object key can not be empty' })
|
return notify({ type: 'error', message: 'object key can not be empty' })
|
||||||
|
|
||||||
|
if (description.length > 256)
|
||||||
|
return notify({ type: 'error', message: 'description can not be longer than 256 characters' })
|
||||||
|
|
||||||
onSave({
|
onSave({
|
||||||
id: chatVar ? chatVar.id : uuid4(),
|
id: chatVar ? chatVar.id : uuid4(),
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ const VariableModal = ({
|
|||||||
// Original check for create new variable
|
// Original check for create new variable
|
||||||
if (!env && envList.some(e => e.name === name))
|
if (!env && envList.some(e => e.name === name))
|
||||||
return notify({ type: 'error', message: 'name is existed' })
|
return notify({ type: 'error', message: 'name is existed' })
|
||||||
|
if (description.length > 256)
|
||||||
|
return notify({ type: 'error', message: 'description can not be longer than 256 characters' })
|
||||||
|
|
||||||
onSave({
|
onSave({
|
||||||
id: env ? env.id : uuid4(),
|
id: env ? env.id : uuid4(),
|
||||||
|
|||||||
Reference in New Issue
Block a user