Compare commits

..

1 Commits

Author SHA1 Message Date
QuantumGhost
51ec835240 fix(api): fix line too long 2025-09-17 23:58:30 +08:00
18 changed files with 1564 additions and 166 deletions

View File

@@ -178,7 +178,6 @@ class PluginDependency(BaseModel):
class Marketplace(BaseModel):
marketplace_plugin_unique_identifier: str
version: str | None = None
@property
def plugin_unique_identifier(self) -> str:
@@ -186,7 +185,6 @@ class PluginDependency(BaseModel):
class Package(BaseModel):
plugin_unique_identifier: str
version: str | None = None
type: Type
value: Github | Marketplace | Package

View File

@@ -156,7 +156,7 @@ def upgrade():
sa.Column('type', sa.String(20), nullable=False),
sa.Column('file_id', models.types.StringUUID(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('workflow_node_execution_offload_pkey')),
sa.UniqueConstraint('node_execution_id', 'type', name=op.f('workflow_node_execution_offload_node_execution_id_key'))
sa.UniqueConstraint('node_execution_id', 'type', name=op.f('workflow_node_execution_offload_node_execution_id_key'), postgresql_nulls_not_distinct=False)
)
with op.batch_alter_table('datasets', schema=None) as batch_op:
batch_op.add_column(sa.Column('keyword_number', sa.Integer(), server_default=sa.text('10'), nullable=True))

View File

@@ -890,18 +890,12 @@ class WorkflowNodeExecutionModel(Base): # This model is expected to have `offlo
class WorkflowNodeExecutionOffload(Base):
__tablename__ = "workflow_node_execution_offload"
__table_args__ = (
# PostgreSQL 14 treats NULL values as distinct in unique constraints by default,
# allowing multiple records with NULL values for the same column combination.
#
# This behavior allows us to have multiple records with NULL node_execution_id,
# simplifying garbage collection process.
UniqueConstraint(
"node_execution_id",
"type",
# Note: PostgreSQL 15+ supports explicit `nulls distinct` behavior through
# `postgresql_nulls_not_distinct=False`, which would make our intention clearer.
# We rely on PostgreSQL's default behavior of treating NULLs as distinct values.
# postgresql_nulls_not_distinct=False,
# Treat `NULL` as distinct for this unique index, so
# we can have mutitple records with `NULL` node_exeution_id, simplify garbage collection process.
postgresql_nulls_not_distinct=False,
),
)
_HASH_COL_SIZE = 64

View File

@@ -1,14 +1,9 @@
import re
from configs import dify_config
from core.helper import marketplace
from core.plugin.entities.plugin import PluginDependency, PluginInstallationSource
from core.plugin.impl.plugin import PluginInstaller
from models.provider_ids import ModelProviderID, ToolProviderID
# Compile regex pattern for version extraction at module level for better performance
_VERSION_REGEX = re.compile(r":(?P<version>[0-9]+(?:\.[0-9]+){2}(?:[+-][0-9A-Za-z.-]+)?)(?:@|$)")
class DependenciesAnalysisService:
@classmethod
@@ -54,13 +49,6 @@ class DependenciesAnalysisService:
for dependency in dependencies:
unique_identifier = dependency.value.plugin_unique_identifier
if unique_identifier in missing_plugin_unique_identifiers:
# Extract version for Marketplace dependencies
if dependency.type == PluginDependency.Type.Marketplace:
version_match = _VERSION_REGEX.search(unique_identifier)
if version_match:
dependency.value.version = version_match.group("version")
# Create and append the dependency (same for all types)
leaked_dependencies.append(
PluginDependency(
type=dependency.type,

View File

@@ -4,7 +4,6 @@ from unittest.mock import create_autospec, patch
import pytest
from faker import Faker
from sqlalchemy import Engine
from werkzeug.exceptions import NotFound
from configs import dify_config
@@ -18,12 +17,6 @@ from services.file_service import FileService
class TestFileService:
"""Integration tests for FileService using testcontainers."""
@pytest.fixture
def engine(self, db_session_with_containers):
bind = db_session_with_containers.get_bind()
assert isinstance(bind, Engine)
return bind
@pytest.fixture
def mock_external_service_dependencies(self):
"""Mock setup for external service dependencies."""
@@ -163,7 +156,7 @@ class TestFileService:
return upload_file
# Test upload_file method
def test_upload_file_success(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_upload_file_success(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test successful file upload with valid parameters.
"""
@@ -174,7 +167,7 @@ class TestFileService:
content = b"test file content"
mimetype = "application/pdf"
upload_file = FileService(engine).upload_file(
upload_file = FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
@@ -194,9 +187,13 @@ class TestFileService:
# Verify storage was called
mock_external_service_dependencies["storage"].save.assert_called_once()
# Verify database state
from extensions.ext_database import db
db.session.refresh(upload_file)
assert upload_file.id is not None
def test_upload_file_with_end_user(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_upload_file_with_end_user(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test file upload with end user instead of account.
"""
@@ -207,7 +204,7 @@ class TestFileService:
content = b"test image content"
mimetype = "image/jpeg"
upload_file = FileService(engine).upload_file(
upload_file = FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
@@ -218,9 +215,7 @@ class TestFileService:
assert upload_file.created_by == end_user.id
assert upload_file.created_by_role == CreatorUserRole.END_USER.value
def test_upload_file_with_datasets_source(
self, db_session_with_containers, engine, mock_external_service_dependencies
):
def test_upload_file_with_datasets_source(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test file upload with datasets source parameter.
"""
@@ -231,7 +226,7 @@ class TestFileService:
content = b"test file content"
mimetype = "application/pdf"
upload_file = FileService(engine).upload_file(
upload_file = FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
@@ -244,7 +239,7 @@ class TestFileService:
assert upload_file.source_url == "https://example.com/source"
def test_upload_file_invalid_filename_characters(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file upload with invalid filename characters.
@@ -257,16 +252,14 @@ class TestFileService:
mimetype = "text/plain"
with pytest.raises(ValueError, match="Filename contains invalid characters"):
FileService(engine).upload_file(
FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
user=account,
)
def test_upload_file_filename_too_long(
self, db_session_with_containers, engine, mock_external_service_dependencies
):
def test_upload_file_filename_too_long(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test file upload with filename that exceeds length limit.
"""
@@ -279,7 +272,7 @@ class TestFileService:
content = b"test content"
mimetype = "text/plain"
upload_file = FileService(engine).upload_file(
upload_file = FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
@@ -295,7 +288,7 @@ class TestFileService:
assert len(base_name) <= 200
def test_upload_file_datasets_unsupported_type(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file upload for datasets with unsupported file type.
@@ -308,7 +301,7 @@ class TestFileService:
mimetype = "image/jpeg"
with pytest.raises(UnsupportedFileTypeError):
FileService(engine).upload_file(
FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
@@ -316,7 +309,7 @@ class TestFileService:
source="datasets",
)
def test_upload_file_too_large(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_upload_file_too_large(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test file upload with file size exceeding limit.
"""
@@ -329,7 +322,7 @@ class TestFileService:
mimetype = "image/jpeg"
with pytest.raises(FileTooLargeError):
FileService(engine).upload_file(
FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
@@ -338,7 +331,7 @@ class TestFileService:
# Test is_file_size_within_limit method
def test_is_file_size_within_limit_image_success(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file size check for image files within limit.
@@ -346,12 +339,12 @@ class TestFileService:
extension = "jpg"
file_size = dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT * 1024 * 1024 # Exactly at limit
result = FileService(engine).is_file_size_within_limit(extension=extension, file_size=file_size)
result = FileService.is_file_size_within_limit(extension=extension, file_size=file_size)
assert result is True
def test_is_file_size_within_limit_video_success(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file size check for video files within limit.
@@ -359,12 +352,12 @@ class TestFileService:
extension = "mp4"
file_size = dify_config.UPLOAD_VIDEO_FILE_SIZE_LIMIT * 1024 * 1024 # Exactly at limit
result = FileService(engine).is_file_size_within_limit(extension=extension, file_size=file_size)
result = FileService.is_file_size_within_limit(extension=extension, file_size=file_size)
assert result is True
def test_is_file_size_within_limit_audio_success(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file size check for audio files within limit.
@@ -372,12 +365,12 @@ class TestFileService:
extension = "mp3"
file_size = dify_config.UPLOAD_AUDIO_FILE_SIZE_LIMIT * 1024 * 1024 # Exactly at limit
result = FileService(engine).is_file_size_within_limit(extension=extension, file_size=file_size)
result = FileService.is_file_size_within_limit(extension=extension, file_size=file_size)
assert result is True
def test_is_file_size_within_limit_document_success(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file size check for document files within limit.
@@ -385,12 +378,12 @@ class TestFileService:
extension = "pdf"
file_size = dify_config.UPLOAD_FILE_SIZE_LIMIT * 1024 * 1024 # Exactly at limit
result = FileService(engine).is_file_size_within_limit(extension=extension, file_size=file_size)
result = FileService.is_file_size_within_limit(extension=extension, file_size=file_size)
assert result is True
def test_is_file_size_within_limit_image_exceeded(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file size check for image files exceeding limit.
@@ -398,12 +391,12 @@ class TestFileService:
extension = "jpg"
file_size = dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT * 1024 * 1024 + 1 # Exceeds limit
result = FileService(engine).is_file_size_within_limit(extension=extension, file_size=file_size)
result = FileService.is_file_size_within_limit(extension=extension, file_size=file_size)
assert result is False
def test_is_file_size_within_limit_unknown_extension(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file size check for unknown file extension.
@@ -411,12 +404,12 @@ class TestFileService:
extension = "xyz"
file_size = dify_config.UPLOAD_FILE_SIZE_LIMIT * 1024 * 1024 # Uses default limit
result = FileService(engine).is_file_size_within_limit(extension=extension, file_size=file_size)
result = FileService.is_file_size_within_limit(extension=extension, file_size=file_size)
assert result is True
# Test upload_text method
def test_upload_text_success(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_upload_text_success(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test successful text upload.
"""
@@ -429,25 +422,21 @@ class TestFileService:
mock_current_user.current_tenant_id = str(fake.uuid4())
mock_current_user.id = str(fake.uuid4())
upload_file = FileService(engine).upload_text(
text=text,
text_name=text_name,
user_id=mock_current_user.id,
tenant_id=mock_current_user.current_tenant_id,
)
with patch("services.file_service.current_user", mock_current_user):
upload_file = FileService.upload_text(text=text, text_name=text_name)
assert upload_file is not None
assert upload_file.name == text_name
assert upload_file.size == len(text)
assert upload_file.extension == "txt"
assert upload_file.mime_type == "text/plain"
assert upload_file.used is True
assert upload_file.used_by == mock_current_user.id
assert upload_file is not None
assert upload_file.name == text_name
assert upload_file.size == len(text)
assert upload_file.extension == "txt"
assert upload_file.mime_type == "text/plain"
assert upload_file.used is True
assert upload_file.used_by == mock_current_user.id
# Verify storage was called
mock_external_service_dependencies["storage"].save.assert_called_once()
# Verify storage was called
mock_external_service_dependencies["storage"].save.assert_called_once()
def test_upload_text_name_too_long(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_upload_text_name_too_long(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test text upload with name that exceeds length limit.
"""
@@ -460,19 +449,15 @@ class TestFileService:
mock_current_user.current_tenant_id = str(fake.uuid4())
mock_current_user.id = str(fake.uuid4())
upload_file = FileService(engine).upload_text(
text=text,
text_name=long_name,
user_id=mock_current_user.id,
tenant_id=mock_current_user.current_tenant_id,
)
with patch("services.file_service.current_user", mock_current_user):
upload_file = FileService.upload_text(text=text, text_name=long_name)
# Verify name was truncated
assert len(upload_file.name) <= 200
assert upload_file.name == "a" * 200
# Verify name was truncated
assert len(upload_file.name) <= 200
assert upload_file.name == "a" * 200
# Test get_file_preview method
def test_get_file_preview_success(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_get_file_preview_success(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test successful file preview generation.
"""
@@ -488,14 +473,12 @@ class TestFileService:
db.session.commit()
result = FileService(engine).get_file_preview(file_id=upload_file.id)
result = FileService.get_file_preview(file_id=upload_file.id)
assert result == "extracted text content"
mock_external_service_dependencies["extract_processor"].load_from_upload_file.assert_called_once()
def test_get_file_preview_file_not_found(
self, db_session_with_containers, engine, mock_external_service_dependencies
):
def test_get_file_preview_file_not_found(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test file preview with non-existent file.
"""
@@ -503,10 +486,10 @@ class TestFileService:
non_existent_id = str(fake.uuid4())
with pytest.raises(NotFound, match="File not found"):
FileService(engine).get_file_preview(file_id=non_existent_id)
FileService.get_file_preview(file_id=non_existent_id)
def test_get_file_preview_unsupported_file_type(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file preview with unsupported file type.
@@ -524,11 +507,9 @@ class TestFileService:
db.session.commit()
with pytest.raises(UnsupportedFileTypeError):
FileService(engine).get_file_preview(file_id=upload_file.id)
FileService.get_file_preview(file_id=upload_file.id)
def test_get_file_preview_text_truncation(
self, db_session_with_containers, engine, mock_external_service_dependencies
):
def test_get_file_preview_text_truncation(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test file preview with text that exceeds preview limit.
"""
@@ -548,13 +529,13 @@ class TestFileService:
long_text = "x" * 5000 # Longer than PREVIEW_WORDS_LIMIT
mock_external_service_dependencies["extract_processor"].load_from_upload_file.return_value = long_text
result = FileService(engine).get_file_preview(file_id=upload_file.id)
result = FileService.get_file_preview(file_id=upload_file.id)
assert len(result) == 3000 # PREVIEW_WORDS_LIMIT
assert result == "x" * 3000
# Test get_image_preview method
def test_get_image_preview_success(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_get_image_preview_success(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test successful image preview generation.
"""
@@ -574,7 +555,7 @@ class TestFileService:
nonce = "test_nonce"
sign = "test_signature"
generator, mime_type = FileService(engine).get_image_preview(
generator, mime_type = FileService.get_image_preview(
file_id=upload_file.id,
timestamp=timestamp,
nonce=nonce,
@@ -585,9 +566,7 @@ class TestFileService:
assert mime_type == upload_file.mime_type
mock_external_service_dependencies["file_helpers"].verify_image_signature.assert_called_once()
def test_get_image_preview_invalid_signature(
self, db_session_with_containers, engine, mock_external_service_dependencies
):
def test_get_image_preview_invalid_signature(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test image preview with invalid signature.
"""
@@ -605,16 +584,14 @@ class TestFileService:
sign = "invalid_signature"
with pytest.raises(NotFound, match="File not found or signature is invalid"):
FileService(engine).get_image_preview(
FileService.get_image_preview(
file_id=upload_file.id,
timestamp=timestamp,
nonce=nonce,
sign=sign,
)
def test_get_image_preview_file_not_found(
self, db_session_with_containers, engine, mock_external_service_dependencies
):
def test_get_image_preview_file_not_found(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test image preview with non-existent file.
"""
@@ -626,7 +603,7 @@ class TestFileService:
sign = "test_signature"
with pytest.raises(NotFound, match="File not found or signature is invalid"):
FileService(engine).get_image_preview(
FileService.get_image_preview(
file_id=non_existent_id,
timestamp=timestamp,
nonce=nonce,
@@ -634,7 +611,7 @@ class TestFileService:
)
def test_get_image_preview_unsupported_file_type(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test image preview with non-image file type.
@@ -656,7 +633,7 @@ class TestFileService:
sign = "test_signature"
with pytest.raises(UnsupportedFileTypeError):
FileService(engine).get_image_preview(
FileService.get_image_preview(
file_id=upload_file.id,
timestamp=timestamp,
nonce=nonce,
@@ -665,7 +642,7 @@ class TestFileService:
# Test get_file_generator_by_file_id method
def test_get_file_generator_by_file_id_success(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test successful file generator retrieval.
@@ -680,7 +657,7 @@ class TestFileService:
nonce = "test_nonce"
sign = "test_signature"
generator, file_obj = FileService(engine).get_file_generator_by_file_id(
generator, file_obj = FileService.get_file_generator_by_file_id(
file_id=upload_file.id,
timestamp=timestamp,
nonce=nonce,
@@ -688,11 +665,11 @@ class TestFileService:
)
assert generator is not None
assert file_obj.id == upload_file.id
assert file_obj == upload_file
mock_external_service_dependencies["file_helpers"].verify_file_signature.assert_called_once()
def test_get_file_generator_by_file_id_invalid_signature(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file generator retrieval with invalid signature.
@@ -711,7 +688,7 @@ class TestFileService:
sign = "invalid_signature"
with pytest.raises(NotFound, match="File not found or signature is invalid"):
FileService(engine).get_file_generator_by_file_id(
FileService.get_file_generator_by_file_id(
file_id=upload_file.id,
timestamp=timestamp,
nonce=nonce,
@@ -719,7 +696,7 @@ class TestFileService:
)
def test_get_file_generator_by_file_id_file_not_found(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file generator retrieval with non-existent file.
@@ -732,7 +709,7 @@ class TestFileService:
sign = "test_signature"
with pytest.raises(NotFound, match="File not found or signature is invalid"):
FileService(engine).get_file_generator_by_file_id(
FileService.get_file_generator_by_file_id(
file_id=non_existent_id,
timestamp=timestamp,
nonce=nonce,
@@ -740,9 +717,7 @@ class TestFileService:
)
# Test get_public_image_preview method
def test_get_public_image_preview_success(
self, db_session_with_containers, engine, mock_external_service_dependencies
):
def test_get_public_image_preview_success(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test successful public image preview generation.
"""
@@ -758,14 +733,14 @@ class TestFileService:
db.session.commit()
generator, mime_type = FileService(engine).get_public_image_preview(file_id=upload_file.id)
generator, mime_type = FileService.get_public_image_preview(file_id=upload_file.id)
assert generator is not None
assert mime_type == upload_file.mime_type
mock_external_service_dependencies["storage"].load.assert_called_once()
def test_get_public_image_preview_file_not_found(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test public image preview with non-existent file.
@@ -774,10 +749,10 @@ class TestFileService:
non_existent_id = str(fake.uuid4())
with pytest.raises(NotFound, match="File not found or signature is invalid"):
FileService(engine).get_public_image_preview(file_id=non_existent_id)
FileService.get_public_image_preview(file_id=non_existent_id)
def test_get_public_image_preview_unsupported_file_type(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test public image preview with non-image file type.
@@ -795,10 +770,10 @@ class TestFileService:
db.session.commit()
with pytest.raises(UnsupportedFileTypeError):
FileService(engine).get_public_image_preview(file_id=upload_file.id)
FileService.get_public_image_preview(file_id=upload_file.id)
# Test edge cases and boundary conditions
def test_upload_file_empty_content(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_upload_file_empty_content(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test file upload with empty content.
"""
@@ -809,7 +784,7 @@ class TestFileService:
content = b""
mimetype = "text/plain"
upload_file = FileService(engine).upload_file(
upload_file = FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
@@ -820,7 +795,7 @@ class TestFileService:
assert upload_file.size == 0
def test_upload_file_special_characters_in_name(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file upload with special characters in filename (but valid ones).
@@ -832,7 +807,7 @@ class TestFileService:
content = b"test content"
mimetype = "text/plain"
upload_file = FileService(engine).upload_file(
upload_file = FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
@@ -843,7 +818,7 @@ class TestFileService:
assert upload_file.name == filename
def test_upload_file_different_case_extensions(
self, db_session_with_containers, engine, mock_external_service_dependencies
self, db_session_with_containers, mock_external_service_dependencies
):
"""
Test file upload with different case extensions.
@@ -855,7 +830,7 @@ class TestFileService:
content = b"test content"
mimetype = "application/pdf"
upload_file = FileService(engine).upload_file(
upload_file = FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
@@ -865,7 +840,7 @@ class TestFileService:
assert upload_file is not None
assert upload_file.extension == "pdf" # Should be converted to lowercase
def test_upload_text_empty_text(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_upload_text_empty_text(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test text upload with empty text.
"""
@@ -878,17 +853,13 @@ class TestFileService:
mock_current_user.current_tenant_id = str(fake.uuid4())
mock_current_user.id = str(fake.uuid4())
upload_file = FileService(engine).upload_text(
text=text,
text_name=text_name,
user_id=mock_current_user.id,
tenant_id=mock_current_user.current_tenant_id,
)
with patch("services.file_service.current_user", mock_current_user):
upload_file = FileService.upload_text(text=text, text_name=text_name)
assert upload_file is not None
assert upload_file.size == 0
assert upload_file is not None
assert upload_file.size == 0
def test_file_size_limits_edge_cases(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_file_size_limits_edge_cases(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test file size limits with edge case values.
"""
@@ -900,15 +871,15 @@ class TestFileService:
("pdf", dify_config.UPLOAD_FILE_SIZE_LIMIT),
]:
file_size = limit_config * 1024 * 1024
result = FileService(engine).is_file_size_within_limit(extension=extension, file_size=file_size)
result = FileService.is_file_size_within_limit(extension=extension, file_size=file_size)
assert result is True
# Test one byte over limit
file_size = limit_config * 1024 * 1024 + 1
result = FileService(engine).is_file_size_within_limit(extension=extension, file_size=file_size)
result = FileService.is_file_size_within_limit(extension=extension, file_size=file_size)
assert result is False
def test_upload_file_with_source_url(self, db_session_with_containers, engine, mock_external_service_dependencies):
def test_upload_file_with_source_url(self, db_session_with_containers, mock_external_service_dependencies):
"""
Test file upload with source URL that gets overridden by signed URL.
"""
@@ -920,7 +891,7 @@ class TestFileService:
mimetype = "application/pdf"
source_url = "https://original-source.com/file.pdf"
upload_file = FileService(engine).upload_file(
upload_file = FileService.upload_file(
filename=filename,
content=content,
mimetype=mimetype,
@@ -933,7 +904,7 @@ class TestFileService:
# The signed URL should only be set when source_url is empty
# Let's test that scenario
upload_file2 = FileService(engine).upload_file(
upload_file2 = FileService.upload_file(
filename="test2.pdf",
content=b"test content 2",
mimetype="application/pdf",

File diff suppressed because it is too large Load Diff

View File

@@ -108,7 +108,6 @@ class TestWorkflowDraftVariableService:
created_by=app.created_by,
environment_variables=[],
conversation_variables=[],
rag_pipeline_variables=[],
)
from extensions.ext_database import db

View File

@@ -149,7 +149,7 @@ export const PortalToFollowElemTrigger = (
context.getReferenceProps({
ref,
...props,
...children.props,
...(children.props || {}),
'data-state': context.open ? 'open' : 'closed',
} as React.HTMLProps<HTMLElement>),
)

View File

@@ -91,7 +91,7 @@ const PageSelector = ({
if (current.expand) {
current.expand = false
newDataList = dataList.filter(item => !descendantsIds.includes(item.page_id))
newDataList = [...dataList.filter(item => !descendantsIds.includes(item.page_id))]
}
else {
current.expand = true
@@ -110,7 +110,7 @@ const PageSelector = ({
}, [dataList, listMapWithChildrenAndDescendants, pagesMap])
const handleCheck = useCallback((index: number) => {
const copyValue = new Set(checkedIds)
const copyValue = new Set([...checkedIds])
const current = currentDataList[index]
const pageId = current.page_id
const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId]
@@ -138,7 +138,7 @@ const PageSelector = ({
}
}
onSelect(new Set(copyValue))
onSelect(new Set([...copyValue]))
}, [currentDataList, isMultipleChoice, listMapWithChildrenAndDescendants, onSelect, searchValue, checkedIds])
const handlePreview = useCallback((index: number) => {

View File

@@ -59,7 +59,7 @@ export const useMarketplaceAllPlugins = (providers: any[], searchText: string) =
}, [queryPlugins, queryPluginsWithDebounced, searchText, exclude])
const allPlugins = useMemo(() => {
const allPlugins = collectionPlugins.filter(plugin => !exclude.includes(plugin.plugin_id))
const allPlugins = [...collectionPlugins.filter(plugin => !exclude.includes(plugin.plugin_id))]
if (plugins?.length) {
for (let i = 0; i < plugins.length; i++) {

View File

@@ -43,7 +43,7 @@ const StrategyDetail: FC<Props> = ({
const outputSchema = useMemo(() => {
const res: any[] = []
if (!detail.output_schema || !detail.output_schema.properties)
if (!detail.output_schema)
return []
Object.keys(detail.output_schema.properties).forEach((outputKey) => {
const output = detail.output_schema.properties[outputKey]

View File

@@ -765,7 +765,7 @@ export const useNodesInteractions = () => {
nodesWithSameType.length > 0
? `${defaultValue.title} ${nodesWithSameType.length + 1}`
: defaultValue.title,
...toolDefaultValue,
...(toolDefaultValue || {}),
selected: true,
_showAddVariablePopup:
(nodeType === BlockEnum.VariableAssigner
@@ -866,7 +866,7 @@ export const useNodesInteractions = () => {
const nodesConnectedSourceOrTargetHandleIdsMap
= getNodesConnectedSourceOrTargetHandleIdsMap(
(newEdge ? [{ type: 'add', edge: newEdge }] : []),
[...(newEdge ? [{ type: 'add', edge: newEdge }] : [])],
nodes,
)
const newNodes = produce(nodes, (draft: Node[]) => {
@@ -1331,7 +1331,7 @@ export const useNodesInteractions = () => {
nodesWithSameType.length > 0
? `${defaultValue.title} ${nodesWithSameType.length + 1}`
: defaultValue.title,
...toolDefaultValue,
...(toolDefaultValue || {}),
_connectedSourceHandleIds: [],
_connectedTargetHandleIds: [],
selected: currentNode.data.selected,

View File

@@ -181,7 +181,7 @@ const useConfig = (id: string, payload: AgentNodeType) => {
const outputSchema = useMemo(() => {
const res: any[] = []
if (!inputs.output_schema || !inputs.output_schema.properties)
if (!inputs.output_schema)
return []
Object.keys(inputs.output_schema.properties).forEach((outputKey) => {
const output = inputs.output_schema.properties[outputKey]

View File

@@ -28,7 +28,7 @@ export const useReplaceDataSourceNode = (id: string) => {
const { newNode } = generateNewNode({
data: {
...(defaultValue as any),
...toolDefaultValue,
...(toolDefaultValue || {}),
},
position: {
x: emptyNode.position.x,

View File

@@ -89,7 +89,7 @@ const nodeDefault: NodeDefault<ToolNodeType> = {
const currTool = currCollection?.tools.find(tool => tool.name === payload.tool_name)
const output_schema = currTool?.output_schema
let res: any[] = []
if (!output_schema || !output_schema.properties) {
if (!output_schema) {
res = TOOL_OUTPUT_STRUCT
}
else {

View File

@@ -65,7 +65,7 @@ const AddBlock = ({
data: {
...(defaultValue as any),
title: nodesWithSameType.length > 0 ? `${defaultValue.title} ${nodesWithSameType.length + 1}` : defaultValue.title,
...toolDefaultValue,
...(toolDefaultValue || {}),
_isCandidate: true,
},
position: {

View File

@@ -23,7 +23,7 @@ import type {
} from '@/types/workflow'
import { removeAccessToken } from '@/app/components/share/utils'
import type { FetchOptionType, ResponseError } from './fetch'
import { ContentType, base, getAccessToken, getBaseOptions } from './fetch'
import { ContentType, base, baseOptions, getAccessToken } from './fetch'
import { asyncRunSafe } from '@/utils'
import type {
DataSourceNodeCompletedResponse,
@@ -400,7 +400,6 @@ export const ssePost = async (
const token = localStorage.getItem('console_token')
const baseOptions = getBaseOptions()
const options = Object.assign({}, baseOptions, {
method: 'POST',
signal: abortController.signal,

View File

@@ -111,7 +111,7 @@ const baseClient = ky.create({
timeout: TIME_OUT,
})
export const getBaseOptions = (): RequestInit => ({
export const baseOptions: RequestInit = {
method: 'GET',
mode: 'cors',
credentials: 'include', // always send cookies、HTTP Basic authentication.
@@ -119,10 +119,9 @@ export const getBaseOptions = (): RequestInit => ({
'Content-Type': ContentType.json,
}),
redirect: 'follow',
})
}
async function base<T>(url: string, options: FetchOptionType = {}, otherOptions: IOtherOptions = {}): Promise<T> {
const baseOptions = getBaseOptions()
const { params, body, headers, ...init } = Object.assign({}, baseOptions, options)
const {
isPublicAPI = false,