mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
fix: validate conversation variable description length to prevent varchar(255) truncation error (#33038)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: 非法操作 <hjlarry@163.com> Co-authored-by: -LAN- <laipz8200@outlook.com>
This commit is contained in:
co-authored by
autofix-ci[bot]
非法操作
-LAN-
parent
09bfbf386e
commit
4fb3210f9a
+1
-1
@@ -8,6 +8,7 @@ import pytest
|
||||
from flask import Flask
|
||||
from werkzeug.exceptions import BadRequest, Forbidden
|
||||
|
||||
from controllers.common.errors import NotFoundError
|
||||
from controllers.console.workspace.trigger_providers import (
|
||||
TriggerOAuthAuthorizeApi,
|
||||
TriggerOAuthCallbackApi,
|
||||
@@ -26,7 +27,6 @@ from controllers.console.workspace.trigger_providers import (
|
||||
TriggerSubscriptionUpdateApi,
|
||||
TriggerSubscriptionVerifyApi,
|
||||
)
|
||||
from controllers.web.error import NotFoundError
|
||||
from core.plugin.entities.plugin_daemon import CredentialType
|
||||
from models.account import Account
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from flask import Flask
|
||||
from pydantic import ValidationError
|
||||
from werkzeug.exceptions import HTTPException, NotFound
|
||||
|
||||
from controllers.common.errors import InvalidArgumentError
|
||||
from controllers.console.app import workflow as workflow_module
|
||||
from controllers.console.app.error import DraftWorkflowNotExist, DraftWorkflowNotSync
|
||||
from graphon.file import File, FileTransferMethod, FileType
|
||||
@@ -180,6 +181,29 @@ def test_sync_draft_workflow_hash_mismatch(app: Flask, monkeypatch: pytest.Monke
|
||||
handler(api, "t1", app_model=SimpleNamespace(id="app"))
|
||||
|
||||
|
||||
def test_sync_draft_workflow_variable_validation_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def _raise(*_args, **_kwargs):
|
||||
raise workflow_module.VariableError("description too long")
|
||||
|
||||
monkeypatch.setattr(workflow_module.variable_factory, "build_conversation_variable_from_mapping", _raise)
|
||||
monkeypatch.setattr(
|
||||
workflow_module, "WorkflowService", lambda: SimpleNamespace(sync_draft_workflow=lambda **_kwargs: None)
|
||||
)
|
||||
|
||||
api = workflow_module.DraftWorkflowApi()
|
||||
handler = inspect.unwrap(api.post)
|
||||
|
||||
with app.test_request_context(
|
||||
"/apps/app/workflows/draft",
|
||||
method="POST",
|
||||
json={"graph": {}, "features": {}, "hash": "h", "conversation_variables": [{"name": "topic"}]},
|
||||
):
|
||||
with pytest.raises(InvalidArgumentError) as exc:
|
||||
handler(api, "t1", app_model=SimpleNamespace(id="app"))
|
||||
|
||||
assert exc.value.description == "description too long"
|
||||
|
||||
|
||||
def test_restore_published_workflow_to_draft_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
workflow = SimpleNamespace(
|
||||
unique_hash="restored-hash",
|
||||
|
||||
@@ -8,8 +8,8 @@ from unittest.mock import Mock
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
from controllers.common.errors import NotFoundError
|
||||
from controllers.console.app import workflow_run as workflow_run_module
|
||||
from controllers.web.error import NotFoundError
|
||||
from graphon.entities.pause_reason import HumanInputRequired
|
||||
from graphon.enums import WorkflowExecutionStatus
|
||||
from graphon.nodes.human_input.entities import ParagraphInputConfig, UserActionConfig
|
||||
|
||||
+1
-1
@@ -3,6 +3,7 @@ from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
from flask import Flask, Response
|
||||
|
||||
from controllers.common.errors import InvalidArgumentError, NotFoundError
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.app.error import DraftWorkflowNotExist
|
||||
from controllers.console.datasets.rag_pipeline.rag_pipeline_draft_variable import (
|
||||
@@ -13,7 +14,6 @@ from controllers.console.datasets.rag_pipeline.rag_pipeline_draft_variable impor
|
||||
RagPipelineVariableCollectionApi,
|
||||
RagPipelineVariableResetApi,
|
||||
)
|
||||
from controllers.web.error import InvalidArgumentError, NotFoundError
|
||||
from core.workflow.variable_prefixes import SYSTEM_VARIABLE_NODE_ID
|
||||
from graphon.variables.types import SegmentType
|
||||
from models.account import Account, TenantAccountRole
|
||||
|
||||
@@ -9,6 +9,7 @@ from unittest.mock import Mock
|
||||
import pytest
|
||||
from flask import Flask, Response
|
||||
|
||||
from controllers.common.errors import NotFoundError
|
||||
from controllers.common.human_input import HumanInputFormSubmitPayload
|
||||
from controllers.console.human_input_form import (
|
||||
ConsoleHumanInputFormApi,
|
||||
@@ -17,7 +18,6 @@ from controllers.console.human_input_form import (
|
||||
WorkflowResponseConverter,
|
||||
_jsonify_form_definition,
|
||||
)
|
||||
from controllers.web.error import NotFoundError
|
||||
from models.account import AccountStatus
|
||||
from models.enums import CreatorUserRole
|
||||
from models.human_input import RecipientType
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from controllers.common.errors import InvalidArgumentError, NotFoundError
|
||||
from controllers.web.error import (
|
||||
AppMoreLikeThisDisabledError,
|
||||
AppSuggestedQuestionsAfterAnswerDisabledError,
|
||||
@@ -11,12 +12,10 @@ from controllers.web.error import (
|
||||
AudioTooLargeError,
|
||||
CompletionRequestError,
|
||||
ConversationCompletedError,
|
||||
InvalidArgumentError,
|
||||
InvokeRateLimitError,
|
||||
NoAudioUploadedError,
|
||||
NotChatAppError,
|
||||
NotCompletionAppError,
|
||||
NotFoundError,
|
||||
NotWorkflowAppError,
|
||||
ProviderModelCurrentlyNotSupportError,
|
||||
ProviderNotInitializeError,
|
||||
|
||||
@@ -8,7 +8,7 @@ from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
from controllers.web.error import NotFoundError
|
||||
from controllers.common.errors import NotFoundError
|
||||
from controllers.web.workflow_events import WorkflowEventsApi
|
||||
from models.enums import CreatorUserRole
|
||||
|
||||
|
||||
@@ -200,6 +200,19 @@ def test_variable_cannot_large_than_200_kb():
|
||||
)
|
||||
|
||||
|
||||
def test_conversation_variable_description_cannot_exceed_255_chars():
|
||||
with pytest.raises(VariableError, match="description of variable 'test_text' is too long"):
|
||||
variable_factory.build_conversation_variable_from_mapping(
|
||||
{
|
||||
"id": str(uuid4()),
|
||||
"value_type": "string",
|
||||
"name": "test_text",
|
||||
"value": "value",
|
||||
"description": "a" * 256,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_array_none_variable():
|
||||
var = variable_factory.build_segment([None, None, None, None])
|
||||
assert isinstance(var, ArrayAnySegment)
|
||||
|
||||
@@ -31,6 +31,7 @@ from services.workflow_draft_variable_service import (
|
||||
DraftVariableSaver,
|
||||
VariableResetError,
|
||||
WorkflowDraftVariableService,
|
||||
_model_to_insertion_dict,
|
||||
)
|
||||
|
||||
|
||||
@@ -643,3 +644,53 @@ class TestWorkflowDraftVariableService:
|
||||
assert node_var.visible == True
|
||||
assert node_var.editable == True
|
||||
assert node_var.node_execution_id == "exec-id"
|
||||
|
||||
|
||||
class TestModelToInsertionDict:
|
||||
"""Reproduce two production errors in _model_to_insertion_dict / _new()."""
|
||||
|
||||
def test_visible_and_is_default_value_always_present(self):
|
||||
"""Problem 1: _new() did not set visible/is_default_value, causing
|
||||
inconsistent dict keys across rows in multi-row INSERT and missing
|
||||
is_default_value in the insertion dict entirely.
|
||||
"""
|
||||
conv_var = WorkflowDraftVariable.new_conversation_variable(
|
||||
app_id="app-1",
|
||||
name="counter",
|
||||
value=StringSegment(value="0"),
|
||||
)
|
||||
# _new() should explicitly set these fields so they are not None
|
||||
assert conv_var.visible is not None
|
||||
assert conv_var.is_default_value is not None
|
||||
|
||||
d = _model_to_insertion_dict(conv_var)
|
||||
# visible must appear in every row's dict
|
||||
assert "visible" in d
|
||||
# is_default_value must always be present
|
||||
assert "is_default_value" in d
|
||||
|
||||
def test_description_passthrough(self):
|
||||
"""_model_to_insertion_dict passes description as-is;
|
||||
length validation is enforced earlier in build_conversation_variable_from_mapping.
|
||||
"""
|
||||
desc = "a" * 200
|
||||
conv_var = WorkflowDraftVariable.new_conversation_variable(
|
||||
app_id="app-1",
|
||||
name="counter",
|
||||
value=StringSegment(value="0"),
|
||||
description=desc,
|
||||
)
|
||||
d = _model_to_insertion_dict(conv_var)
|
||||
assert d["description"] == desc
|
||||
|
||||
def test_is_default_value_omitted_when_none(self):
|
||||
conv_var = WorkflowDraftVariable.new_conversation_variable(
|
||||
app_id="app-1",
|
||||
name="counter",
|
||||
value=StringSegment(value="0"),
|
||||
)
|
||||
conv_var.is_default_value = None
|
||||
|
||||
d = _model_to_insertion_dict(conv_var)
|
||||
|
||||
assert "is_default_value" not in d
|
||||
|
||||
Reference in New Issue
Block a user