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:
Novice
2026-06-10 07:28:12 +00:00
committed by GitHub
co-authored by autofix-ci[bot] 非法操作 -LAN-
parent 09bfbf386e
commit 4fb3210f9a
31 changed files with 206 additions and 26 deletions
@@ -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
@@ -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