mirror of https://github.com/langgenius/dify.git
parent
37041f3746
commit
3d0ec1fedb
|
|
@ -481,9 +481,12 @@ class PluginModelRuntime(ModelRuntime):
|
|||
) -> str:
|
||||
cache_key = f"{self.tenant_id}:{provider}:{model_type.value}:{model}"
|
||||
sorted_credentials = sorted(credentials.items()) if credentials else []
|
||||
return cache_key + ":".join(
|
||||
if not sorted_credentials:
|
||||
return cache_key
|
||||
hashed_credentials = ":".join(
|
||||
[hashlib.md5(f"{key}:{value}".encode()).hexdigest() for key, value in sorted_credentials]
|
||||
)
|
||||
return f"{cache_key}:{hashed_credentials}"
|
||||
|
||||
def _split_provider(self, provider: str) -> tuple[str, str]:
|
||||
provider_id = ModelProviderID(provider)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any
|
||||
|
||||
|
|
@ -11,6 +13,8 @@ from . import helpers
|
|||
from .constants import FILE_MODEL_IDENTITY
|
||||
from .enums import FileTransferMethod, FileType
|
||||
|
||||
_FILE_REFERENCE_PREFIX = "dify-file-ref:"
|
||||
|
||||
|
||||
def sign_tool_file(*, tool_file_id: str, extension: str, for_external: bool = True) -> str:
|
||||
"""Compatibility shim for tests and legacy callers patching ``models.sign_tool_file``."""
|
||||
|
|
@ -43,6 +47,31 @@ class FileUploadConfig(BaseModel):
|
|||
number_limits: int = 0
|
||||
|
||||
|
||||
def _parse_reference(reference: str | None) -> tuple[str | None, str | None]:
|
||||
"""Best-effort parser for legacy aliases backed by the opaque file reference."""
|
||||
if not reference:
|
||||
return None, None
|
||||
|
||||
if not reference.startswith(_FILE_REFERENCE_PREFIX):
|
||||
return reference, None
|
||||
|
||||
encoded_payload = reference.removeprefix(_FILE_REFERENCE_PREFIX)
|
||||
try:
|
||||
payload = json.loads(base64.urlsafe_b64decode(encoded_payload.encode()))
|
||||
except (ValueError, json.JSONDecodeError):
|
||||
return reference, None
|
||||
|
||||
record_id = payload.get("record_id")
|
||||
if not isinstance(record_id, str) or not record_id:
|
||||
return reference, None
|
||||
|
||||
storage_key = payload.get("storage_key")
|
||||
if not isinstance(storage_key, str):
|
||||
storage_key = None
|
||||
|
||||
return record_id, storage_key
|
||||
|
||||
|
||||
class File(BaseModel):
|
||||
"""Graph-owned file reference.
|
||||
|
||||
|
|
@ -67,11 +96,13 @@ class File(BaseModel):
|
|||
extension: str | None = Field(default=None, description="File extension, should contain dot")
|
||||
mime_type: str | None = None
|
||||
size: int = -1
|
||||
_storage_key: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: str | None = None,
|
||||
tenant_id: str | None = None,
|
||||
type: FileType,
|
||||
transfer_method: FileTransferMethod,
|
||||
remote_url: str | None = None,
|
||||
|
|
@ -89,10 +120,11 @@ class File(BaseModel):
|
|||
upload_file_id: str | None = None,
|
||||
datasource_file_id: str | None = None,
|
||||
):
|
||||
legacy_record_id = tool_file_id or upload_file_id or datasource_file_id or related_id
|
||||
legacy_record_id = related_id or tool_file_id or upload_file_id or datasource_file_id
|
||||
normalized_reference = reference
|
||||
if normalized_reference is None and legacy_record_id is not None:
|
||||
normalized_reference = str(legacy_record_id)
|
||||
_, parsed_storage_key = _parse_reference(normalized_reference)
|
||||
|
||||
super().__init__(
|
||||
id=id,
|
||||
|
|
@ -107,12 +139,15 @@ class File(BaseModel):
|
|||
dify_model_identity=dify_model_identity,
|
||||
url=url,
|
||||
)
|
||||
# Accept legacy constructor fields without promoting them back into the graph model.
|
||||
_ = tenant_id
|
||||
self._storage_key = storage_key or parsed_storage_key or ""
|
||||
|
||||
def to_dict(self) -> Mapping[str, str | int | None]:
|
||||
data = self.model_dump(mode="json")
|
||||
return {
|
||||
**data,
|
||||
"related_id": self.reference,
|
||||
"related_id": self.related_id,
|
||||
"url": self.generate_url(),
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +196,8 @@ class File(BaseModel):
|
|||
|
||||
@property
|
||||
def related_id(self) -> str | None:
|
||||
return self.reference
|
||||
record_id, _ = _parse_reference(self.reference)
|
||||
return record_id
|
||||
|
||||
@related_id.setter
|
||||
def related_id(self, value: str | None) -> None:
|
||||
|
|
@ -169,4 +205,9 @@ class File(BaseModel):
|
|||
|
||||
@property
|
||||
def storage_key(self) -> str:
|
||||
return ""
|
||||
_, storage_key = _parse_reference(self.reference)
|
||||
return storage_key or self._storage_key
|
||||
|
||||
@storage_key.setter
|
||||
def storage_key(self, value: str) -> None:
|
||||
self._storage_key = value
|
||||
|
|
|
|||
|
|
@ -207,13 +207,7 @@ class VariablePool(BaseModel):
|
|||
return result
|
||||
|
||||
def flatten(self, *, unprefixed_node_id: str | None = None) -> Mapping[str, object]:
|
||||
"""Return a selector-style snapshot of the entire variable pool.
|
||||
|
||||
Variables belonging to ``unprefixed_node_id`` keep their original names so callers
|
||||
can expose the current node's values without duplicating its namespace. All other
|
||||
entries are emitted as ``"<node_id>.<name>"`` to preserve their source prefix in a
|
||||
single flat mapping.
|
||||
"""
|
||||
"""Return a selector-style snapshot of the entire variable pool."""
|
||||
|
||||
result: dict[str, object] = {}
|
||||
for node_id, variables in self.variable_dictionary.items():
|
||||
|
|
|
|||
|
|
@ -570,6 +570,7 @@ class StorageKeyLoader:
|
|||
record_id=str(upload_file_row.id),
|
||||
storage_key=upload_file_row.key,
|
||||
)
|
||||
file.storage_key = upload_file_row.key
|
||||
elif file.transfer_method == FileTransferMethod.TOOL_FILE:
|
||||
tool_file_row = tool_files.get(model_id)
|
||||
if tool_file_row is None:
|
||||
|
|
@ -578,3 +579,4 @@ class StorageKeyLoader:
|
|||
record_id=str(tool_file_row.id),
|
||||
storage_key=tool_file_row.file_key,
|
||||
)
|
||||
file.storage_key = tool_file_row.file_key
|
||||
|
|
|
|||
|
|
@ -898,22 +898,7 @@ class DraftVariableSaver:
|
|||
for name, value in output.items():
|
||||
value_seg = _build_segment_for_serialized_values(value)
|
||||
node_id, name = self._normalize_variable_for_start_node(name)
|
||||
if node_id == self._node_id:
|
||||
# Variables without a reserved prefix belong to the Start node itself.
|
||||
draft_vars.append(
|
||||
WorkflowDraftVariable.new_node_variable(
|
||||
app_id=self._app_id,
|
||||
user_id=self._user.id,
|
||||
node_id=self._node_id,
|
||||
name=name,
|
||||
node_execution_id=self._node_execution_id,
|
||||
value=value_seg,
|
||||
visible=True,
|
||||
editable=True,
|
||||
)
|
||||
)
|
||||
has_non_sys_variables = True
|
||||
elif node_id == SYSTEM_VARIABLE_NODE_ID:
|
||||
if node_id == SYSTEM_VARIABLE_NODE_ID:
|
||||
if name == SystemVariableKey.FILES:
|
||||
# Here we know the type of variable must be `array[file]`, we
|
||||
# just build files from the value.
|
||||
|
|
@ -947,6 +932,7 @@ class DraftVariableSaver:
|
|||
value=value_seg,
|
||||
)
|
||||
)
|
||||
has_non_sys_variables = True
|
||||
else:
|
||||
draft_vars.append(
|
||||
WorkflowDraftVariable.new_node_variable(
|
||||
|
|
@ -960,6 +946,7 @@ class DraftVariableSaver:
|
|||
editable=self._should_variable_be_editable(node_id, name),
|
||||
)
|
||||
)
|
||||
has_non_sys_variables = True
|
||||
if not has_non_sys_variables:
|
||||
draft_vars.append(self._create_dummy_output_variable())
|
||||
return draft_vars
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ class TestStorageKeyLoader(unittest.TestCase):
|
|||
|
||||
return File(
|
||||
id=str(uuid4()), # Generate new UUID for File.id
|
||||
tenant_id=tenant_id,
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=transfer_method,
|
||||
related_id=file_related_id,
|
||||
|
|
@ -191,19 +192,16 @@ class TestStorageKeyLoader(unittest.TestCase):
|
|||
# Should not raise any exceptions
|
||||
self.loader.load_storage_keys([])
|
||||
|
||||
def test_load_storage_keys_tenant_mismatch(self):
|
||||
"""Test tenant_id validation."""
|
||||
# Create file with different tenant_id
|
||||
def test_load_storage_keys_ignores_legacy_file_tenant_id(self):
|
||||
"""Legacy file tenant_id should not override the loader tenant scope."""
|
||||
upload_file = self._create_upload_file()
|
||||
file = self._create_file(
|
||||
related_id=upload_file.id, transfer_method=FileTransferMethod.LOCAL_FILE, tenant_id=str(uuid4())
|
||||
)
|
||||
|
||||
# Should raise ValueError for tenant mismatch
|
||||
with pytest.raises(ValueError) as context:
|
||||
self.loader.load_storage_keys([file])
|
||||
self.loader.load_storage_keys([file])
|
||||
|
||||
assert "invalid file, expected tenant_id" in str(context.value)
|
||||
assert file._storage_key == upload_file.key
|
||||
|
||||
def test_load_storage_keys_missing_file_id(self):
|
||||
"""Test with None file.related_id."""
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ class TestStorageKeyLoader(unittest.TestCase):
|
|||
|
||||
return File(
|
||||
id=str(uuid4()), # Generate new UUID for File.id
|
||||
tenant_id=tenant_id,
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=transfer_method,
|
||||
related_id=file_related_id,
|
||||
|
|
@ -192,19 +193,16 @@ class TestStorageKeyLoader(unittest.TestCase):
|
|||
# Should not raise any exceptions
|
||||
self.loader.load_storage_keys([])
|
||||
|
||||
def test_load_storage_keys_tenant_mismatch(self):
|
||||
"""Test tenant_id validation."""
|
||||
# Create file with different tenant_id
|
||||
def test_load_storage_keys_ignores_legacy_file_tenant_id(self):
|
||||
"""Legacy file tenant_id should not override the loader tenant scope."""
|
||||
upload_file = self._create_upload_file()
|
||||
file = self._create_file(
|
||||
related_id=upload_file.id, transfer_method=FileTransferMethod.LOCAL_FILE, tenant_id=str(uuid4())
|
||||
)
|
||||
|
||||
# Should raise ValueError for tenant mismatch
|
||||
with pytest.raises(ValueError) as context:
|
||||
self.loader.load_storage_keys([file])
|
||||
self.loader.load_storage_keys([file])
|
||||
|
||||
assert "invalid file, expected tenant_id" in str(context.value)
|
||||
assert file._storage_key == upload_file.key
|
||||
|
||||
def test_load_storage_keys_missing_file_id(self):
|
||||
"""Test with None file.related_id."""
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ def test_parse_file_with_config(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
config = object()
|
||||
file_list = [
|
||||
File(
|
||||
tenant_id="t1",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="http://u",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import pytest
|
||||
|
||||
from dify_graph.file import File, FileTransferMethod, FileType
|
||||
|
||||
|
||||
def test_file():
|
||||
file = File(
|
||||
id="test-file",
|
||||
tenant_id="test-tenant-id",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.TOOL_FILE,
|
||||
related_id="test-related-id",
|
||||
|
|
@ -19,13 +18,14 @@ def test_file():
|
|||
assert file.type == FileType.IMAGE
|
||||
assert file.transfer_method == FileTransferMethod.TOOL_FILE
|
||||
assert file.related_id == "test-related-id"
|
||||
assert file.storage_key == "test-storage-key"
|
||||
assert file.filename == "image.png"
|
||||
assert file.extension == ".png"
|
||||
assert file.mime_type == "image/png"
|
||||
assert file.size == 67
|
||||
|
||||
|
||||
def test_file_model_validate_rejects_removed_tenant_id():
|
||||
def test_file_model_validate_accepts_legacy_tenant_id():
|
||||
data = {
|
||||
"id": "test-file",
|
||||
"tenant_id": "test-tenant-id",
|
||||
|
|
@ -44,5 +44,8 @@ def test_file_model_validate_rejects_removed_tenant_id():
|
|||
"datasource_file_id": "datasource-file-789",
|
||||
}
|
||||
|
||||
with pytest.raises(TypeError, match="tenant_id"):
|
||||
File.model_validate(data)
|
||||
file = File.model_validate(data)
|
||||
|
||||
assert file.related_id == "test-related-id"
|
||||
assert file.storage_key == "test-storage-key"
|
||||
assert "tenant_id" not in file.model_dump()
|
||||
|
|
|
|||
|
|
@ -466,6 +466,7 @@ class TestChunkMerger:
|
|||
class TestConverter:
|
||||
def test_convert_parameters_to_plugin_format_with_single_file_and_selector(self):
|
||||
file_param = File(
|
||||
tenant_id="tenant-1",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://example.com/file.png",
|
||||
|
|
@ -498,12 +499,14 @@ class TestConverter:
|
|||
|
||||
def test_convert_parameters_to_plugin_format_with_lists_and_passthrough_values(self):
|
||||
file_one = File(
|
||||
tenant_id="tenant-1",
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://example.com/a.txt",
|
||||
storage_key="",
|
||||
)
|
||||
file_two = File(
|
||||
tenant_id="tenant-1",
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://example.com/b.txt",
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ def test__get_chat_model_prompt_messages_with_files_no_memory(get_chat_model_arg
|
|||
files = [
|
||||
File(
|
||||
id="file1",
|
||||
tenant_id="tenant1",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://example.com/image1.jpg",
|
||||
|
|
@ -245,6 +246,7 @@ def test_completion_prompt_jinja2_with_files():
|
|||
|
||||
file = File(
|
||||
id="file1",
|
||||
tenant_id="tenant1",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://example.com/image.jpg",
|
||||
|
|
@ -378,6 +380,7 @@ def test_chat_prompt_memory_with_files_and_query():
|
|||
prompt_template = [ChatModelMessage(text="sys", role=PromptMessageRole.SYSTEM)]
|
||||
file = File(
|
||||
id="file1",
|
||||
tenant_id="tenant1",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://example.com/image.jpg",
|
||||
|
|
@ -411,6 +414,7 @@ def test_chat_prompt_files_without_query_updates_last_user_or_appends_new():
|
|||
model_config_mock = MagicMock(spec=ModelConfigEntity)
|
||||
file = File(
|
||||
id="file1",
|
||||
tenant_id="tenant1",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://example.com/image.jpg",
|
||||
|
|
@ -460,6 +464,7 @@ def test_chat_prompt_files_with_query_branch():
|
|||
model_config_mock = MagicMock(spec=ModelConfigEntity)
|
||||
file = File(
|
||||
id="file1",
|
||||
tenant_id="tenant1",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://example.com/image.jpg",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from models.workflow import Workflow
|
|||
def test_file_to_dict():
|
||||
file = File(
|
||||
id="file1",
|
||||
tenant_id="tenant1",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://example.com/image1.jpg",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ def create_test_file(
|
|||
) -> File:
|
||||
"""Factory function to create File objects for testing."""
|
||||
return File(
|
||||
tenant_id="test-tenant",
|
||||
type=file_type,
|
||||
transfer_method=transfer_method,
|
||||
filename=filename,
|
||||
|
|
|
|||
|
|
@ -227,6 +227,7 @@ def test_build_segment_array_file_single_file():
|
|||
"""Test building ArrayFileSegment from list with single file."""
|
||||
file = File(
|
||||
id="test_file_id",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://test.example.com/test-file.png",
|
||||
|
|
@ -246,6 +247,7 @@ def test_build_segment_array_file_multiple_files():
|
|||
"""Test building ArrayFileSegment from list with multiple files."""
|
||||
file1 = File(
|
||||
id="test_file_id_1",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://test.example.com/test-file1.png",
|
||||
|
|
@ -256,6 +258,7 @@ def test_build_segment_array_file_multiple_files():
|
|||
)
|
||||
file2 = File(
|
||||
id="test_file_id_2",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||
related_id="test_relation_id",
|
||||
|
|
@ -302,6 +305,7 @@ def test_build_segment_array_any_mixed_with_files():
|
|||
"""Test building ArrayAnySegment from list with files and other types."""
|
||||
file = File(
|
||||
id="test_file_id",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://test.example.com/test-file.png",
|
||||
|
|
@ -330,6 +334,7 @@ def test_build_segment_array_file_properties():
|
|||
"""Test ArrayFileSegment properties and methods."""
|
||||
file1 = File(
|
||||
id="test_file_id_1",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://test.example.com/test-file1.png",
|
||||
|
|
@ -340,6 +345,7 @@ def test_build_segment_array_file_properties():
|
|||
)
|
||||
file2 = File(
|
||||
id="test_file_id_2",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://test.example.com/test-file2.txt",
|
||||
|
|
@ -388,6 +394,7 @@ def test_build_segment_file_array_with_different_file_types():
|
|||
"""Test ArrayFileSegment with different file types."""
|
||||
image_file = File(
|
||||
id="image_id",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://test.example.com/image.png",
|
||||
|
|
@ -399,6 +406,7 @@ def test_build_segment_file_array_with_different_file_types():
|
|||
|
||||
video_file = File(
|
||||
id="video_id",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.VIDEO,
|
||||
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||
related_id="video_relation_id",
|
||||
|
|
@ -410,6 +418,7 @@ def test_build_segment_file_array_with_different_file_types():
|
|||
|
||||
audio_file = File(
|
||||
id="audio_id",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.AUDIO,
|
||||
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||
related_id="audio_relation_id",
|
||||
|
|
@ -447,6 +456,7 @@ def _generate_file(draw) -> File:
|
|||
url = "https://test.example.com/test-file"
|
||||
file = File(
|
||||
id="test_file_id",
|
||||
tenant_id="test_tenant_id",
|
||||
type=file_type,
|
||||
transfer_method=transfer_method,
|
||||
remote_url=url,
|
||||
|
|
@ -461,6 +471,7 @@ def _generate_file(draw) -> File:
|
|||
|
||||
file = File(
|
||||
id="test_file_id",
|
||||
tenant_id="test_tenant_id",
|
||||
type=file_type,
|
||||
transfer_method=transfer_method,
|
||||
related_id=str(relation_id),
|
||||
|
|
@ -508,6 +519,7 @@ def test_build_segment_type_for_scalar():
|
|||
|
||||
file = File(
|
||||
id="test_file_id",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://test.example.com/test-file.png",
|
||||
|
|
@ -564,6 +576,7 @@ class TestBuildSegmentWithType:
|
|||
"""Test building a file segment with correct type."""
|
||||
test_file = File(
|
||||
id="test_file_id",
|
||||
tenant_id="test_tenant_id",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.REMOTE_URL,
|
||||
remote_url="https://test.example.com/test-file.png",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from types import SimpleNamespace
|
|||
|
||||
import pytest
|
||||
|
||||
from core.workflow.file_reference import build_file_reference
|
||||
from dify_graph.file import File, FileTransferMethod, FileType
|
||||
from fields import conversation_fields, message_fields
|
||||
from fields.file_fields import FileResponse, FileWithSignedUrl, RemoteFileInfo, UploadConfig
|
||||
|
|
@ -91,12 +92,13 @@ def test_remote_file_info_and_upload_config() -> None:
|
|||
)
|
||||
def test_file_formatters_preserve_legacy_file_keys(monkeypatch: pytest.MonkeyPatch, formatter) -> None:
|
||||
monkeypatch.setattr(File, "generate_url", lambda self, for_external=True: "https://preview.example/file")
|
||||
reference = build_file_reference(record_id="upload-1", storage_key="files/source.pdf")
|
||||
|
||||
file = File(
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||
remote_url="https://storage.example/source.pdf",
|
||||
reference="dify-file-ref:opaque-upload-1",
|
||||
reference=reference,
|
||||
filename="source.pdf",
|
||||
extension=".pdf",
|
||||
mime_type="application/pdf",
|
||||
|
|
@ -105,7 +107,7 @@ def test_file_formatters_preserve_legacy_file_keys(monkeypatch: pytest.MonkeyPat
|
|||
|
||||
serialized = formatter(file)
|
||||
|
||||
assert serialized["reference"] == "dify-file-ref:opaque-upload-1"
|
||||
assert serialized["related_id"] == "dify-file-ref:opaque-upload-1"
|
||||
assert serialized["reference"] == reference
|
||||
assert serialized["related_id"] == "upload-1"
|
||||
assert serialized["remote_url"] == "https://storage.example/source.pdf"
|
||||
assert serialized["url"] == "https://preview.example/file"
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ from services.variable_truncator import (
|
|||
def file() -> File:
|
||||
return File(
|
||||
id=str(uuid4()), # Generate new UUID for File.id
|
||||
tenant_id=str(uuid.uuid4()),
|
||||
type=FileType.DOCUMENT,
|
||||
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||
related_id=str(uuid.uuid4()),
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ class TestDraftVariableSaver:
|
|||
mock_batch_upsert.assert_called_once()
|
||||
draft_vars = mock_batch_upsert.call_args[0][1]
|
||||
|
||||
assert len(draft_vars) == 4
|
||||
assert len(draft_vars) == 3
|
||||
|
||||
env_var = next(v for v in draft_vars if v.node_id == ENVIRONMENT_VARIABLE_NODE_ID)
|
||||
assert env_var.name == "API_KEY"
|
||||
|
|
|
|||
Loading…
Reference in New Issue