mirror of
https://github.com/langgenius/dify.git
synced 2026-01-01 12:07:11 +00:00
Compare commits
1 Commits
feat/rag-2
...
feat/end_u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51ec835240 |
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -108,7 +108,6 @@ class TestWorkflowDraftVariableService:
|
||||
created_by=app.created_by,
|
||||
environment_variables=[],
|
||||
conversation_variables=[],
|
||||
rag_pipeline_variables=[],
|
||||
)
|
||||
from extensions.ext_database import db
|
||||
|
||||
|
||||
@@ -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>),
|
||||
)
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -28,7 +28,7 @@ export const useReplaceDataSourceNode = (id: string) => {
|
||||
const { newNode } = generateNewNode({
|
||||
data: {
|
||||
...(defaultValue as any),
|
||||
...toolDefaultValue,
|
||||
...(toolDefaultValue || {}),
|
||||
},
|
||||
position: {
|
||||
x: emptyNode.position.x,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user