mirror of
https://github.com/langgenius/dify.git
synced 2026-04-30 07:28:05 +08:00
Merge remote-tracking branch 'origin/feat/plugins' into dev/plugin-deploy
This commit is contained in:
@ -1,4 +1,7 @@
|
||||
from core.file import FILE_MODEL_IDENTITY, File, FileTransferMethod, FileType
|
||||
import json
|
||||
|
||||
from core.file import FILE_MODEL_IDENTITY, File, FileTransferMethod, FileType, FileUploadConfig
|
||||
from models.workflow import Workflow
|
||||
|
||||
|
||||
def test_file_loads_and_dumps():
|
||||
@ -38,3 +41,40 @@ def test_file_to_dict():
|
||||
file_dict = file.to_dict()
|
||||
assert "_extra_config" not in file_dict
|
||||
assert "url" in file_dict
|
||||
|
||||
|
||||
def test_workflow_features_with_image():
|
||||
# Create a feature dict that mimics the old structure with image config
|
||||
features = {
|
||||
"file_upload": {
|
||||
"image": {"enabled": True, "number_limits": 5, "transfer_methods": ["remote_url", "local_file"]}
|
||||
}
|
||||
}
|
||||
|
||||
# Create a workflow instance with the features
|
||||
workflow = Workflow(
|
||||
tenant_id="tenant-1",
|
||||
app_id="app-1",
|
||||
type="chat",
|
||||
version="1.0",
|
||||
graph="{}",
|
||||
features=json.dumps(features),
|
||||
created_by="user-1",
|
||||
environment_variables=[],
|
||||
conversation_variables=[],
|
||||
)
|
||||
|
||||
# Get the converted features through the property
|
||||
converted_features = json.loads(workflow.features)
|
||||
|
||||
# Create FileUploadConfig from the converted features
|
||||
file_upload_config = FileUploadConfig.model_validate(converted_features["file_upload"])
|
||||
|
||||
# Validate the config
|
||||
assert file_upload_config.number_limits == 5
|
||||
assert list(file_upload_config.allowed_file_types) == [FileType.IMAGE]
|
||||
assert list(file_upload_config.allowed_file_upload_methods) == [
|
||||
FileTransferMethod.REMOTE_URL,
|
||||
FileTransferMethod.LOCAL_FILE,
|
||||
]
|
||||
assert list(file_upload_config.allowed_file_extensions) == []
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import redis
|
||||
|
||||
from core.entities.provider_entities import ModelLoadBalancingConfiguration
|
||||
from core.model_manager import LBModelManager
|
||||
from core.model_runtime.entities.model_entities import ModelType
|
||||
from extensions.ext_redis import redis_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -38,6 +40,9 @@ def lb_model_manager():
|
||||
|
||||
|
||||
def test_lb_model_manager_fetch_next(mocker, lb_model_manager):
|
||||
# initialize redis client
|
||||
redis_client.initialize(redis.Redis())
|
||||
|
||||
assert len(lb_model_manager._load_balancing_configs) == 3
|
||||
|
||||
config1 = lb_model_manager._load_balancing_configs[0]
|
||||
@ -55,12 +60,13 @@ def test_lb_model_manager_fetch_next(mocker, lb_model_manager):
|
||||
start_index += 1
|
||||
return start_index
|
||||
|
||||
mocker.patch("redis.Redis.incr", side_effect=incr)
|
||||
mocker.patch("redis.Redis.set", return_value=None)
|
||||
mocker.patch("redis.Redis.expire", return_value=None)
|
||||
with (
|
||||
patch.object(redis_client, "incr", side_effect=incr),
|
||||
patch.object(redis_client, "set", return_value=None),
|
||||
patch.object(redis_client, "expire", return_value=None),
|
||||
):
|
||||
config = lb_model_manager.fetch_next()
|
||||
assert config == config2
|
||||
|
||||
config = lb_model_manager.fetch_next()
|
||||
assert config == config2
|
||||
|
||||
config = lb_model_manager.fetch_next()
|
||||
assert config == config3
|
||||
config = lb_model_manager.fetch_next()
|
||||
assert config == config3
|
||||
|
||||
@ -0,0 +1,140 @@
|
||||
from unittest.mock import Mock, PropertyMock, patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from core.workflow.nodes.http_request.entities import Response
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_response():
|
||||
response = Mock(spec=httpx.Response)
|
||||
response.headers = {}
|
||||
return response
|
||||
|
||||
|
||||
def test_is_file_with_attachment_disposition(mock_response):
|
||||
"""Test is_file when content-disposition header contains 'attachment'"""
|
||||
mock_response.headers = {"content-disposition": "attachment; filename=test.pdf", "content-type": "application/pdf"}
|
||||
response = Response(mock_response)
|
||||
assert response.is_file
|
||||
|
||||
|
||||
def test_is_file_with_filename_disposition(mock_response):
|
||||
"""Test is_file when content-disposition header contains filename parameter"""
|
||||
mock_response.headers = {"content-disposition": "inline; filename=test.pdf", "content-type": "application/pdf"}
|
||||
response = Response(mock_response)
|
||||
assert response.is_file
|
||||
|
||||
|
||||
@pytest.mark.parametrize("content_type", ["application/pdf", "image/jpeg", "audio/mp3", "video/mp4"])
|
||||
def test_is_file_with_file_content_types(mock_response, content_type):
|
||||
"""Test is_file with various file content types"""
|
||||
mock_response.headers = {"content-type": content_type}
|
||||
# Mock binary content
|
||||
type(mock_response).content = PropertyMock(return_value=bytes([0x00, 0xFF] * 512))
|
||||
response = Response(mock_response)
|
||||
assert response.is_file, f"Content type {content_type} should be identified as a file"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"content_type",
|
||||
[
|
||||
"application/json",
|
||||
"application/xml",
|
||||
"application/javascript",
|
||||
"application/x-www-form-urlencoded",
|
||||
"application/yaml",
|
||||
"application/graphql",
|
||||
],
|
||||
)
|
||||
def test_text_based_application_types(mock_response, content_type):
|
||||
"""Test common text-based application types are not identified as files"""
|
||||
mock_response.headers = {"content-type": content_type}
|
||||
response = Response(mock_response)
|
||||
assert not response.is_file, f"Content type {content_type} should not be identified as a file"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("content", "content_type"),
|
||||
[
|
||||
(b'{"key": "value"}', "application/octet-stream"),
|
||||
(b"[1, 2, 3]", "application/unknown"),
|
||||
(b"function test() {}", "application/x-unknown"),
|
||||
(b"<root>test</root>", "application/binary"),
|
||||
(b"var x = 1;", "application/data"),
|
||||
],
|
||||
)
|
||||
def test_content_based_detection(mock_response, content, content_type):
|
||||
"""Test content-based detection for text-like content"""
|
||||
mock_response.headers = {"content-type": content_type}
|
||||
type(mock_response).content = PropertyMock(return_value=content)
|
||||
response = Response(mock_response)
|
||||
assert not response.is_file, f"Content {content} with type {content_type} should not be identified as a file"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("content", "content_type"),
|
||||
[
|
||||
(bytes([0x00, 0xFF] * 512), "application/octet-stream"),
|
||||
(bytes([0x89, 0x50, 0x4E, 0x47]), "application/unknown"), # PNG magic numbers
|
||||
(bytes([0xFF, 0xD8, 0xFF]), "application/binary"), # JPEG magic numbers
|
||||
],
|
||||
)
|
||||
def test_binary_content_detection(mock_response, content, content_type):
|
||||
"""Test content-based detection for binary content"""
|
||||
mock_response.headers = {"content-type": content_type}
|
||||
type(mock_response).content = PropertyMock(return_value=content)
|
||||
response = Response(mock_response)
|
||||
assert response.is_file, f"Binary content with type {content_type} should be identified as a file"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("content_type", "expected_main_type"),
|
||||
[
|
||||
("x-world/x-vrml", "model"), # VRML 3D model
|
||||
("font/ttf", "application"), # TrueType font
|
||||
("text/csv", "text"), # CSV text file
|
||||
("unknown/xyz", None), # Unknown type
|
||||
],
|
||||
)
|
||||
def test_mimetype_based_detection(mock_response, content_type, expected_main_type):
|
||||
"""Test detection using mimetypes.guess_type for non-application content types"""
|
||||
mock_response.headers = {"content-type": content_type}
|
||||
type(mock_response).content = PropertyMock(return_value=bytes([0x00])) # Dummy content
|
||||
|
||||
with patch("core.workflow.nodes.http_request.entities.mimetypes.guess_type") as mock_guess_type:
|
||||
# Mock the return value based on expected_main_type
|
||||
if expected_main_type:
|
||||
mock_guess_type.return_value = (f"{expected_main_type}/subtype", None)
|
||||
else:
|
||||
mock_guess_type.return_value = (None, None)
|
||||
|
||||
response = Response(mock_response)
|
||||
|
||||
# Check if the result matches our expectation
|
||||
if expected_main_type in ("application", "image", "audio", "video"):
|
||||
assert response.is_file, f"Content type {content_type} should be identified as a file"
|
||||
else:
|
||||
assert not response.is_file, f"Content type {content_type} should not be identified as a file"
|
||||
|
||||
# Verify that guess_type was called
|
||||
mock_guess_type.assert_called_once()
|
||||
|
||||
|
||||
def test_is_file_with_inline_disposition(mock_response):
|
||||
"""Test is_file when content-disposition is 'inline'"""
|
||||
mock_response.headers = {"content-disposition": "inline", "content-type": "application/pdf"}
|
||||
# Mock binary content
|
||||
type(mock_response).content = PropertyMock(return_value=bytes([0x00, 0xFF] * 512))
|
||||
response = Response(mock_response)
|
||||
assert response.is_file
|
||||
|
||||
|
||||
def test_is_file_with_no_content_disposition(mock_response):
|
||||
"""Test is_file when no content-disposition header is present"""
|
||||
mock_response.headers = {"content-type": "application/pdf"}
|
||||
# Mock binary content
|
||||
type(mock_response).content = PropertyMock(return_value=bytes([0x00, 0xFF] * 512))
|
||||
response = Response(mock_response)
|
||||
assert response.is_file
|
||||
20
api/tests/unit_tests/utils/test_text_processing.py
Normal file
20
api/tests/unit_tests/utils/test_text_processing.py
Normal file
@ -0,0 +1,20 @@
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
from core.tools.utils.text_processing_utils import remove_leading_symbols
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("input_text", "expected_output"),
|
||||
[
|
||||
("...Hello, World!", "Hello, World!"),
|
||||
("。测试中文标点", "测试中文标点"),
|
||||
("!@#Test symbols", "Test symbols"),
|
||||
("Hello, World!", "Hello, World!"),
|
||||
("", ""),
|
||||
(" ", " "),
|
||||
],
|
||||
)
|
||||
def test_remove_leading_symbols(input_text, expected_output):
|
||||
assert remove_leading_symbols(input_text) == expected_output
|
||||
Reference in New Issue
Block a user