mirror of
https://github.com/langgenius/dify.git
synced 2026-03-11 03:29:44 +08:00
test: unit test cases for controllers.files, controllers.mcp and controllers.trigger module (#32057)
This commit is contained in:
parent
6ff420cd03
commit
212756c315
@ -64,6 +64,10 @@ class ToolFileApi(Resource):
|
||||
|
||||
if not stream or not tool_file:
|
||||
raise NotFound("file is not found")
|
||||
|
||||
except NotFound:
|
||||
raise
|
||||
|
||||
except Exception:
|
||||
raise UnsupportedFileTypeError()
|
||||
|
||||
|
||||
211
api/tests/unit_tests/controllers/files/test_image_preview.py
Normal file
211
api/tests/unit_tests/controllers/files/test_image_preview.py
Normal file
@ -0,0 +1,211 @@
|
||||
import types
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
import controllers.files.image_preview as module
|
||||
|
||||
|
||||
def unwrap(func):
|
||||
while hasattr(func, "__wrapped__"):
|
||||
func = func.__wrapped__
|
||||
return func
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_db():
|
||||
"""
|
||||
Replace Flask-SQLAlchemy db with a plain object
|
||||
to avoid touching Flask app context entirely.
|
||||
"""
|
||||
fake_db = types.SimpleNamespace(engine=object())
|
||||
module.db = fake_db
|
||||
|
||||
|
||||
class DummyUploadFile:
|
||||
def __init__(self, mime_type="text/plain", size=10, name="test.txt", extension="txt"):
|
||||
self.mime_type = mime_type
|
||||
self.size = size
|
||||
self.name = name
|
||||
self.extension = extension
|
||||
|
||||
|
||||
def fake_request(args: dict):
|
||||
"""Return a fake request object (NOT a Flask LocalProxy)."""
|
||||
return types.SimpleNamespace(args=types.SimpleNamespace(to_dict=lambda flat=True: args))
|
||||
|
||||
|
||||
class TestImagePreviewApi:
|
||||
@patch.object(module, "FileService")
|
||||
def test_success(self, mock_file_service):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
}
|
||||
)
|
||||
|
||||
generator = iter([b"img"])
|
||||
mock_file_service.return_value.get_image_preview.return_value = (
|
||||
generator,
|
||||
"image/png",
|
||||
)
|
||||
|
||||
api = module.ImagePreviewApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
response = get_fn("file-id")
|
||||
|
||||
assert response.mimetype == "image/png"
|
||||
|
||||
@patch.object(module, "FileService")
|
||||
def test_unsupported_file_type(self, mock_file_service):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
}
|
||||
)
|
||||
|
||||
mock_file_service.return_value.get_image_preview.side_effect = (
|
||||
module.services.errors.file.UnsupportedFileTypeError()
|
||||
)
|
||||
|
||||
api = module.ImagePreviewApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
with pytest.raises(module.UnsupportedFileTypeError):
|
||||
get_fn("file-id")
|
||||
|
||||
|
||||
class TestFilePreviewApi:
|
||||
@patch.object(module, "enforce_download_for_html")
|
||||
@patch.object(module, "FileService")
|
||||
def test_basic_stream(self, mock_file_service, mock_enforce):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"as_attachment": False,
|
||||
}
|
||||
)
|
||||
|
||||
generator = iter([b"data"])
|
||||
upload_file = DummyUploadFile(size=100)
|
||||
|
||||
mock_file_service.return_value.get_file_generator_by_file_id.return_value = (
|
||||
generator,
|
||||
upload_file,
|
||||
)
|
||||
|
||||
api = module.FilePreviewApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
response = get_fn("file-id")
|
||||
|
||||
assert response.mimetype == "text/plain"
|
||||
assert response.headers["Content-Length"] == "100"
|
||||
assert "Accept-Ranges" not in response.headers
|
||||
mock_enforce.assert_called_once()
|
||||
|
||||
@patch.object(module, "enforce_download_for_html")
|
||||
@patch.object(module, "FileService")
|
||||
def test_as_attachment(self, mock_file_service, mock_enforce):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"as_attachment": True,
|
||||
}
|
||||
)
|
||||
|
||||
generator = iter([b"data"])
|
||||
upload_file = DummyUploadFile(
|
||||
mime_type="application/pdf",
|
||||
name="doc.pdf",
|
||||
extension="pdf",
|
||||
)
|
||||
|
||||
mock_file_service.return_value.get_file_generator_by_file_id.return_value = (
|
||||
generator,
|
||||
upload_file,
|
||||
)
|
||||
|
||||
api = module.FilePreviewApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
response = get_fn("file-id")
|
||||
|
||||
assert response.headers["Content-Disposition"].startswith("attachment")
|
||||
assert response.headers["Content-Type"] == "application/octet-stream"
|
||||
mock_enforce.assert_called_once()
|
||||
|
||||
@patch.object(module, "FileService")
|
||||
def test_unsupported_file_type(self, mock_file_service):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"as_attachment": False,
|
||||
}
|
||||
)
|
||||
|
||||
mock_file_service.return_value.get_file_generator_by_file_id.side_effect = (
|
||||
module.services.errors.file.UnsupportedFileTypeError()
|
||||
)
|
||||
|
||||
api = module.FilePreviewApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
with pytest.raises(module.UnsupportedFileTypeError):
|
||||
get_fn("file-id")
|
||||
|
||||
|
||||
class TestWorkspaceWebappLogoApi:
|
||||
@patch.object(module, "FileService")
|
||||
@patch.object(module.TenantService, "get_custom_config")
|
||||
def test_success(self, mock_config, mock_file_service):
|
||||
mock_config.return_value = {"replace_webapp_logo": "logo-id"}
|
||||
generator = iter([b"logo"])
|
||||
|
||||
mock_file_service.return_value.get_public_image_preview.return_value = (
|
||||
generator,
|
||||
"image/png",
|
||||
)
|
||||
|
||||
api = module.WorkspaceWebappLogoApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
response = get_fn("workspace-id")
|
||||
|
||||
assert response.mimetype == "image/png"
|
||||
|
||||
@patch.object(module.TenantService, "get_custom_config")
|
||||
def test_logo_not_configured(self, mock_config):
|
||||
mock_config.return_value = {}
|
||||
|
||||
api = module.WorkspaceWebappLogoApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
with pytest.raises(NotFound):
|
||||
get_fn("workspace-id")
|
||||
|
||||
@patch.object(module, "FileService")
|
||||
@patch.object(module.TenantService, "get_custom_config")
|
||||
def test_unsupported_file_type(self, mock_config, mock_file_service):
|
||||
mock_config.return_value = {"replace_webapp_logo": "logo-id"}
|
||||
mock_file_service.return_value.get_public_image_preview.side_effect = (
|
||||
module.services.errors.file.UnsupportedFileTypeError()
|
||||
)
|
||||
|
||||
api = module.WorkspaceWebappLogoApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
with pytest.raises(module.UnsupportedFileTypeError):
|
||||
get_fn("workspace-id")
|
||||
173
api/tests/unit_tests/controllers/files/test_tool_files.py
Normal file
173
api/tests/unit_tests/controllers/files/test_tool_files.py
Normal file
@ -0,0 +1,173 @@
|
||||
import types
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from werkzeug.exceptions import Forbidden, NotFound
|
||||
|
||||
import controllers.files.tool_files as module
|
||||
|
||||
|
||||
def unwrap(func):
|
||||
while hasattr(func, "__wrapped__"):
|
||||
func = func.__wrapped__
|
||||
return func
|
||||
|
||||
|
||||
def fake_request(args: dict):
|
||||
return types.SimpleNamespace(args=types.SimpleNamespace(to_dict=lambda flat=True: args))
|
||||
|
||||
|
||||
class DummyToolFile:
|
||||
def __init__(self, mimetype="text/plain", size=10, name="tool.txt"):
|
||||
self.mimetype = mimetype
|
||||
self.size = size
|
||||
self.name = name
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_global_db():
|
||||
fake_db = types.SimpleNamespace(engine=object())
|
||||
module.global_db = fake_db
|
||||
|
||||
|
||||
class TestToolFileApi:
|
||||
@patch.object(module, "verify_tool_file_signature", return_value=True)
|
||||
@patch.object(module, "ToolFileManager")
|
||||
def test_success_stream(
|
||||
self,
|
||||
mock_tool_file_manager,
|
||||
mock_verify,
|
||||
):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"as_attachment": False,
|
||||
}
|
||||
)
|
||||
|
||||
stream = iter([b"data"])
|
||||
tool_file = DummyToolFile(size=100)
|
||||
|
||||
mock_tool_file_manager.return_value.get_file_generator_by_tool_file_id.return_value = (
|
||||
stream,
|
||||
tool_file,
|
||||
)
|
||||
|
||||
api = module.ToolFileApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
response = get_fn("file-id", "txt")
|
||||
|
||||
assert response.mimetype == "text/plain"
|
||||
assert response.headers["Content-Length"] == "100"
|
||||
mock_verify.assert_called_once_with(
|
||||
file_id="file-id",
|
||||
timestamp="123",
|
||||
nonce="abc",
|
||||
sign="sig",
|
||||
)
|
||||
|
||||
@patch.object(module, "verify_tool_file_signature", return_value=True)
|
||||
@patch.object(module, "ToolFileManager")
|
||||
def test_as_attachment(
|
||||
self,
|
||||
mock_tool_file_manager,
|
||||
mock_verify,
|
||||
):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"as_attachment": True,
|
||||
}
|
||||
)
|
||||
|
||||
stream = iter([b"data"])
|
||||
tool_file = DummyToolFile(
|
||||
mimetype="application/pdf",
|
||||
name="doc.pdf",
|
||||
)
|
||||
|
||||
mock_tool_file_manager.return_value.get_file_generator_by_tool_file_id.return_value = (
|
||||
stream,
|
||||
tool_file,
|
||||
)
|
||||
|
||||
api = module.ToolFileApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
response = get_fn("file-id", "pdf")
|
||||
|
||||
assert response.headers["Content-Disposition"].startswith("attachment")
|
||||
mock_verify.assert_called_once()
|
||||
|
||||
@patch.object(module, "verify_tool_file_signature", return_value=False)
|
||||
def test_invalid_signature(self, mock_verify):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "bad-sig",
|
||||
"as_attachment": False,
|
||||
}
|
||||
)
|
||||
|
||||
api = module.ToolFileApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
with pytest.raises(Forbidden):
|
||||
get_fn("file-id", "txt")
|
||||
|
||||
@patch.object(module, "verify_tool_file_signature", return_value=True)
|
||||
@patch.object(module, "ToolFileManager")
|
||||
def test_file_not_found(
|
||||
self,
|
||||
mock_tool_file_manager,
|
||||
mock_verify,
|
||||
):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"as_attachment": False,
|
||||
}
|
||||
)
|
||||
|
||||
mock_tool_file_manager.return_value.get_file_generator_by_tool_file_id.return_value = (
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
||||
api = module.ToolFileApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
with pytest.raises(NotFound):
|
||||
get_fn("file-id", "txt")
|
||||
|
||||
@patch.object(module, "verify_tool_file_signature", return_value=True)
|
||||
@patch.object(module, "ToolFileManager")
|
||||
def test_unsupported_file_type(
|
||||
self,
|
||||
mock_tool_file_manager,
|
||||
mock_verify,
|
||||
):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"as_attachment": False,
|
||||
}
|
||||
)
|
||||
|
||||
mock_tool_file_manager.return_value.get_file_generator_by_tool_file_id.side_effect = Exception("boom")
|
||||
|
||||
api = module.ToolFileApi()
|
||||
get_fn = unwrap(api.get)
|
||||
|
||||
with pytest.raises(module.UnsupportedFileTypeError):
|
||||
get_fn("file-id", "txt")
|
||||
189
api/tests/unit_tests/controllers/files/test_upload.py
Normal file
189
api/tests/unit_tests/controllers/files/test_upload.py
Normal file
@ -0,0 +1,189 @@
|
||||
import types
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
import controllers.files.upload as module
|
||||
|
||||
|
||||
def unwrap(func):
|
||||
while hasattr(func, "__wrapped__"):
|
||||
func = func.__wrapped__
|
||||
return func
|
||||
|
||||
|
||||
def fake_request(args: dict, file=None):
|
||||
return types.SimpleNamespace(
|
||||
args=types.SimpleNamespace(to_dict=lambda flat=True: args),
|
||||
files={"file": file} if file else {},
|
||||
)
|
||||
|
||||
|
||||
class DummyUser:
|
||||
def __init__(self, user_id="user-1"):
|
||||
self.id = user_id
|
||||
|
||||
|
||||
class DummyFile:
|
||||
def __init__(self, filename="test.txt", mimetype="text/plain", content=b"data"):
|
||||
self.filename = filename
|
||||
self.mimetype = mimetype
|
||||
self._content = content
|
||||
|
||||
def read(self):
|
||||
return self._content
|
||||
|
||||
|
||||
class DummyToolFile:
|
||||
def __init__(self):
|
||||
self.id = "file-id"
|
||||
self.name = "test.txt"
|
||||
self.size = 10
|
||||
self.mimetype = "text/plain"
|
||||
self.original_url = "http://original"
|
||||
self.user_id = "user-1"
|
||||
self.tenant_id = "tenant-1"
|
||||
self.conversation_id = None
|
||||
self.file_key = "file-key"
|
||||
|
||||
|
||||
class TestPluginUploadFileApi:
|
||||
@patch.object(module, "verify_plugin_file_signature", return_value=True)
|
||||
@patch.object(module, "get_user", return_value=DummyUser())
|
||||
@patch.object(module, "ToolFileManager")
|
||||
def test_success_upload(
|
||||
self,
|
||||
mock_tool_file_manager,
|
||||
mock_get_user,
|
||||
mock_verify_signature,
|
||||
):
|
||||
dummy_file = DummyFile()
|
||||
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"tenant_id": "tenant-1",
|
||||
"user_id": "user-1",
|
||||
},
|
||||
file=dummy_file,
|
||||
)
|
||||
|
||||
tool_file_manager_instance = mock_tool_file_manager.return_value
|
||||
tool_file_manager_instance.create_file_by_raw.return_value = DummyToolFile()
|
||||
|
||||
mock_tool_file_manager.sign_file.return_value = "signed-url"
|
||||
|
||||
api = module.PluginUploadFileApi()
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
result, status_code = post_fn(api)
|
||||
|
||||
assert status_code == 201
|
||||
assert result["id"] == "file-id"
|
||||
assert result["preview_url"] == "signed-url"
|
||||
|
||||
def test_missing_file(self):
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"tenant_id": "tenant-1",
|
||||
"user_id": "user-1",
|
||||
}
|
||||
)
|
||||
|
||||
api = module.PluginUploadFileApi()
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(Forbidden):
|
||||
post_fn(api)
|
||||
|
||||
@patch.object(module, "get_user", return_value=DummyUser())
|
||||
@patch.object(module, "verify_plugin_file_signature", return_value=False)
|
||||
def test_invalid_signature(self, mock_verify, mock_get_user):
|
||||
dummy_file = DummyFile()
|
||||
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "bad",
|
||||
"tenant_id": "tenant-1",
|
||||
"user_id": "user-1",
|
||||
},
|
||||
file=dummy_file,
|
||||
)
|
||||
|
||||
api = module.PluginUploadFileApi()
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(Forbidden):
|
||||
post_fn(api)
|
||||
|
||||
@patch.object(module, "get_user", return_value=DummyUser())
|
||||
@patch.object(module, "verify_plugin_file_signature", return_value=True)
|
||||
@patch.object(module, "ToolFileManager")
|
||||
def test_file_too_large(
|
||||
self,
|
||||
mock_tool_file_manager,
|
||||
mock_verify,
|
||||
mock_get_user,
|
||||
):
|
||||
dummy_file = DummyFile()
|
||||
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"tenant_id": "tenant-1",
|
||||
"user_id": "user-1",
|
||||
},
|
||||
file=dummy_file,
|
||||
)
|
||||
|
||||
mock_tool_file_manager.return_value.create_file_by_raw.side_effect = (
|
||||
module.services.errors.file.FileTooLargeError("too large")
|
||||
)
|
||||
|
||||
api = module.PluginUploadFileApi()
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.FileTooLargeError):
|
||||
post_fn(api)
|
||||
|
||||
@patch.object(module, "get_user", return_value=DummyUser())
|
||||
@patch.object(module, "verify_plugin_file_signature", return_value=True)
|
||||
@patch.object(module, "ToolFileManager")
|
||||
def test_unsupported_file_type(
|
||||
self,
|
||||
mock_tool_file_manager,
|
||||
mock_verify,
|
||||
mock_get_user,
|
||||
):
|
||||
dummy_file = DummyFile()
|
||||
|
||||
module.request = fake_request(
|
||||
{
|
||||
"timestamp": "123",
|
||||
"nonce": "abc",
|
||||
"sign": "sig",
|
||||
"tenant_id": "tenant-1",
|
||||
"user_id": "user-1",
|
||||
},
|
||||
file=dummy_file,
|
||||
)
|
||||
|
||||
mock_tool_file_manager.return_value.create_file_by_raw.side_effect = (
|
||||
module.services.errors.file.UnsupportedFileTypeError()
|
||||
)
|
||||
|
||||
api = module.PluginUploadFileApi()
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.UnsupportedFileTypeError):
|
||||
post_fn(api)
|
||||
508
api/tests/unit_tests/controllers/mcp/test_mcp.py
Normal file
508
api/tests/unit_tests/controllers/mcp/test_mcp.py
Normal file
@ -0,0 +1,508 @@
|
||||
import types
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from flask import Response
|
||||
from pydantic import ValidationError
|
||||
|
||||
import controllers.mcp.mcp as module
|
||||
|
||||
|
||||
def unwrap(func):
|
||||
while hasattr(func, "__wrapped__"):
|
||||
func = func.__wrapped__
|
||||
return func
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_db():
|
||||
module.db = types.SimpleNamespace(engine=object())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_session():
|
||||
session = MagicMock()
|
||||
session.__enter__.return_value = session
|
||||
session.__exit__.return_value = False
|
||||
return session
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_session(fake_session):
|
||||
module.Session = MagicMock(return_value=fake_session)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_mcp_ns():
|
||||
fake_ns = types.SimpleNamespace()
|
||||
fake_ns.payload = None
|
||||
fake_ns.models = {}
|
||||
module.mcp_ns = fake_ns
|
||||
|
||||
|
||||
def fake_payload(data):
|
||||
module.mcp_ns.payload = data
|
||||
|
||||
|
||||
class DummyServer:
|
||||
def __init__(self, status, app_id="app-1", tenant_id="tenant-1", server_id="srv-1"):
|
||||
self.status = status
|
||||
self.app_id = app_id
|
||||
self.tenant_id = tenant_id
|
||||
self.id = server_id
|
||||
|
||||
|
||||
class DummyApp:
|
||||
def __init__(self, mode, workflow=None, app_model_config=None):
|
||||
self.id = "app-1"
|
||||
self.tenant_id = "tenant-1"
|
||||
self.mode = mode
|
||||
self.workflow = workflow
|
||||
self.app_model_config = app_model_config
|
||||
|
||||
|
||||
class DummyWorkflow:
|
||||
def user_input_form(self, to_old_structure=False):
|
||||
return []
|
||||
|
||||
|
||||
class DummyConfig:
|
||||
def to_dict(self):
|
||||
return {"user_input_form": []}
|
||||
|
||||
|
||||
class DummyResult:
|
||||
def model_dump(self, **kwargs):
|
||||
return {"jsonrpc": "2.0", "result": "ok", "id": 1}
|
||||
|
||||
|
||||
class TestMCPAppApi:
|
||||
@patch.object(module, "handle_mcp_request", return_value=DummyResult())
|
||||
def test_success_request(self, mock_handle):
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "test-client", "version": "1.0"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
response = post_fn("server-1")
|
||||
|
||||
assert isinstance(response, Response)
|
||||
mock_handle.assert_called_once()
|
||||
|
||||
def test_notification_initialized(self):
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/initialized",
|
||||
"params": {},
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
response = post_fn("server-1")
|
||||
|
||||
assert response.status_code == 202
|
||||
|
||||
def test_invalid_notification_method(self):
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/invalid",
|
||||
"params": {},
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.MCPRequestError):
|
||||
post_fn("server-1")
|
||||
|
||||
def test_inactive_server(self):
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "test",
|
||||
"id": 1,
|
||||
"params": {},
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status="inactive")
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.MCPRequestError):
|
||||
post_fn("server-1")
|
||||
|
||||
def test_invalid_payload(self):
|
||||
fake_payload({"invalid": "data"})
|
||||
|
||||
api = module.MCPAppApi()
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
post_fn("server-1")
|
||||
|
||||
def test_missing_request_id(self):
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "test",
|
||||
"params": {},
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.WORKFLOW,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.MCPRequestError):
|
||||
post_fn("server-1")
|
||||
|
||||
def test_server_not_found(self):
|
||||
"""Test when MCP server doesn't exist"""
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "test-client", "version": "1.0"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(
|
||||
side_effect=module.MCPRequestError(module.mcp_types.INVALID_REQUEST, "Server Not Found")
|
||||
)
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.MCPRequestError) as exc_info:
|
||||
post_fn("server-1")
|
||||
assert "Server Not Found" in str(exc_info.value)
|
||||
|
||||
def test_app_not_found(self):
|
||||
"""Test when app associated with server doesn't exist"""
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "test-client", "version": "1.0"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(
|
||||
side_effect=module.MCPRequestError(module.mcp_types.INVALID_REQUEST, "App Not Found")
|
||||
)
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.MCPRequestError) as exc_info:
|
||||
post_fn("server-1")
|
||||
assert "App Not Found" in str(exc_info.value)
|
||||
|
||||
def test_app_unavailable_no_workflow(self):
|
||||
"""Test when app has no workflow (ADVANCED_CHAT mode)"""
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "test-client", "version": "1.0"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=None, # No workflow
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.MCPRequestError) as exc_info:
|
||||
post_fn("server-1")
|
||||
assert "App is unavailable" in str(exc_info.value)
|
||||
|
||||
def test_app_unavailable_no_model_config(self):
|
||||
"""Test when app has no model config (chat mode)"""
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "test-client", "version": "1.0"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.CHAT,
|
||||
app_model_config=None, # No model config
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.MCPRequestError) as exc_info:
|
||||
post_fn("server-1")
|
||||
assert "App is unavailable" in str(exc_info.value)
|
||||
|
||||
@patch.object(module, "handle_mcp_request", return_value=None)
|
||||
def test_mcp_request_no_response(self, mock_handle):
|
||||
"""Test when handle_mcp_request returns None"""
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "test-client", "version": "1.0"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.MCPRequestError) as exc_info:
|
||||
post_fn("server-1")
|
||||
assert "No response generated" in str(exc_info.value)
|
||||
|
||||
def test_workflow_mode_with_user_input_form(self):
|
||||
"""Test WORKFLOW mode app with user input form"""
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "test-client", "version": "1.0"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
class WorkflowWithForm:
|
||||
def user_input_form(self, to_old_structure=False):
|
||||
return [{"text-input": {"variable": "test_var", "label": "Test"}}]
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.WORKFLOW,
|
||||
workflow=WorkflowWithForm(),
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
with patch.object(module, "handle_mcp_request", return_value=DummyResult()):
|
||||
post_fn = unwrap(api.post)
|
||||
response = post_fn("server-1")
|
||||
assert isinstance(response, Response)
|
||||
|
||||
def test_chat_mode_with_model_config(self):
|
||||
"""Test CHAT mode app with model config"""
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "test-client", "version": "1.0"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.CHAT,
|
||||
app_model_config=DummyConfig(),
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
with patch.object(module, "handle_mcp_request", return_value=DummyResult()):
|
||||
post_fn = unwrap(api.post)
|
||||
response = post_fn("server-1")
|
||||
assert isinstance(response, Response)
|
||||
|
||||
def test_invalid_mcp_request_format(self):
|
||||
"""Test invalid MCP request that doesn't match any type"""
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "invalid_method_xyz",
|
||||
"id": 1,
|
||||
"params": {},
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.MCPRequestError) as exc_info:
|
||||
post_fn("server-1")
|
||||
assert "Invalid MCP request" in str(exc_info.value)
|
||||
|
||||
def test_server_found_successfully(self):
|
||||
"""Test successful server and app retrieval"""
|
||||
api = module.MCPAppApi()
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
|
||||
session = MagicMock()
|
||||
session.query().where().first.side_effect = [server, app]
|
||||
|
||||
result_server, result_app = api._get_mcp_server_and_app("server-1", session)
|
||||
|
||||
assert result_server == server
|
||||
assert result_app == app
|
||||
|
||||
def test_validate_server_status_active(self):
|
||||
"""Test successful server status validation"""
|
||||
api = module.MCPAppApi()
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
|
||||
# Should not raise an exception
|
||||
api._validate_server_status(server)
|
||||
|
||||
def test_convert_user_input_form_empty(self):
|
||||
"""Test converting empty user input form"""
|
||||
api = module.MCPAppApi()
|
||||
result = api._convert_user_input_form([])
|
||||
assert result == []
|
||||
|
||||
def test_invalid_user_input_form_validation(self):
|
||||
"""Test invalid user input form that fails validation"""
|
||||
fake_payload(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "test-client", "version": "1.0"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
class WorkflowWithBadForm:
|
||||
def user_input_form(self, to_old_structure=False):
|
||||
# Invalid type that will fail validation
|
||||
return [{"invalid-type": {"variable": "test_var"}}]
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.WORKFLOW,
|
||||
workflow=WorkflowWithBadForm(),
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
|
||||
post_fn = unwrap(api.post)
|
||||
|
||||
with pytest.raises(module.MCPRequestError) as exc_info:
|
||||
post_fn("server-1")
|
||||
assert "Invalid user_input_form" in str(exc_info.value)
|
||||
73
api/tests/unit_tests/controllers/trigger/test_trigger.py
Normal file
73
api/tests/unit_tests/controllers/trigger/test_trigger.py
Normal file
@ -0,0 +1,73 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
import controllers.trigger.trigger as module
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_request():
|
||||
module.request = object()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_jsonify():
|
||||
module.jsonify = lambda payload: payload
|
||||
|
||||
|
||||
VALID_UUID = "123e4567-e89b-42d3-a456-426614174000"
|
||||
INVALID_UUID = "not-a-uuid"
|
||||
|
||||
|
||||
class TestTriggerEndpoint:
|
||||
def test_invalid_uuid(self):
|
||||
with pytest.raises(NotFound):
|
||||
module.trigger_endpoint(INVALID_UUID)
|
||||
|
||||
@patch.object(module.TriggerService, "process_endpoint")
|
||||
@patch.object(module.TriggerSubscriptionBuilderService, "process_builder_validation_endpoint")
|
||||
def test_first_handler_returns_response(self, mock_builder, mock_trigger):
|
||||
mock_trigger.return_value = ("ok", 200)
|
||||
mock_builder.return_value = None
|
||||
|
||||
response = module.trigger_endpoint(VALID_UUID)
|
||||
|
||||
assert response == ("ok", 200)
|
||||
mock_builder.assert_not_called()
|
||||
|
||||
@patch.object(module.TriggerService, "process_endpoint")
|
||||
@patch.object(module.TriggerSubscriptionBuilderService, "process_builder_validation_endpoint")
|
||||
def test_second_handler_returns_response(self, mock_builder, mock_trigger):
|
||||
mock_trigger.return_value = None
|
||||
mock_builder.return_value = ("ok", 200)
|
||||
|
||||
response = module.trigger_endpoint(VALID_UUID)
|
||||
|
||||
assert response == ("ok", 200)
|
||||
|
||||
@patch.object(module.TriggerService, "process_endpoint")
|
||||
@patch.object(module.TriggerSubscriptionBuilderService, "process_builder_validation_endpoint")
|
||||
def test_no_handler_returns_response(self, mock_builder, mock_trigger):
|
||||
mock_trigger.return_value = None
|
||||
mock_builder.return_value = None
|
||||
|
||||
response, status = module.trigger_endpoint(VALID_UUID)
|
||||
|
||||
assert status == 404
|
||||
assert response["error"] == "Endpoint not found"
|
||||
|
||||
@patch.object(module.TriggerService, "process_endpoint", side_effect=ValueError("bad input"))
|
||||
def test_value_error(self, mock_trigger):
|
||||
response, status = module.trigger_endpoint(VALID_UUID)
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "Endpoint processing failed"
|
||||
assert response["message"] == "bad input"
|
||||
|
||||
@patch.object(module.TriggerService, "process_endpoint", side_effect=Exception("boom"))
|
||||
def test_unexpected_exception(self, mock_trigger):
|
||||
response, status = module.trigger_endpoint(VALID_UUID)
|
||||
|
||||
assert status == 500
|
||||
assert response["error"] == "Internal server error"
|
||||
152
api/tests/unit_tests/controllers/trigger/test_webhook.py
Normal file
152
api/tests/unit_tests/controllers/trigger/test_webhook.py
Normal file
@ -0,0 +1,152 @@
|
||||
import types
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from werkzeug.exceptions import NotFound, RequestEntityTooLarge
|
||||
|
||||
import controllers.trigger.webhook as module
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_request():
|
||||
module.request = types.SimpleNamespace(
|
||||
method="POST",
|
||||
headers={"x-test": "1"},
|
||||
args={"a": "b"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_jsonify():
|
||||
module.jsonify = lambda payload: payload
|
||||
|
||||
|
||||
class DummyWebhookTrigger:
|
||||
webhook_id = "wh-1"
|
||||
tenant_id = "tenant-1"
|
||||
app_id = "app-1"
|
||||
node_id = "node-1"
|
||||
|
||||
|
||||
class TestPrepareWebhookExecution:
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow")
|
||||
@patch.object(module.WebhookService, "extract_and_validate_webhook_data")
|
||||
def test_prepare_success(self, mock_extract, mock_get):
|
||||
mock_get.return_value = ("trigger", "workflow", "node_config")
|
||||
mock_extract.return_value = {"data": "ok"}
|
||||
|
||||
result = module._prepare_webhook_execution("wh-1")
|
||||
|
||||
assert result == ("trigger", "workflow", "node_config", {"data": "ok"}, None)
|
||||
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow")
|
||||
@patch.object(module.WebhookService, "extract_and_validate_webhook_data", side_effect=ValueError("bad"))
|
||||
def test_prepare_validation_error(self, mock_extract, mock_get):
|
||||
mock_get.return_value = ("trigger", "workflow", "node_config")
|
||||
|
||||
trigger, workflow, node_config, webhook_data, error = module._prepare_webhook_execution("wh-1")
|
||||
|
||||
assert error == "bad"
|
||||
assert webhook_data["method"] == "POST"
|
||||
|
||||
|
||||
class TestHandleWebhook:
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow")
|
||||
@patch.object(module.WebhookService, "extract_and_validate_webhook_data")
|
||||
@patch.object(module.WebhookService, "trigger_workflow_execution")
|
||||
@patch.object(module.WebhookService, "generate_webhook_response")
|
||||
def test_success(
|
||||
self,
|
||||
mock_generate,
|
||||
mock_trigger,
|
||||
mock_extract,
|
||||
mock_get,
|
||||
):
|
||||
mock_get.return_value = (DummyWebhookTrigger(), "workflow", "node_config")
|
||||
mock_extract.return_value = {"input": "x"}
|
||||
mock_generate.return_value = ({"ok": True}, 200)
|
||||
|
||||
response, status = module.handle_webhook("wh-1")
|
||||
|
||||
assert status == 200
|
||||
assert response["ok"] is True
|
||||
mock_trigger.assert_called_once()
|
||||
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow")
|
||||
@patch.object(module.WebhookService, "extract_and_validate_webhook_data", side_effect=ValueError("bad"))
|
||||
def test_bad_request(self, mock_extract, mock_get):
|
||||
mock_get.return_value = (DummyWebhookTrigger(), "workflow", "node_config")
|
||||
|
||||
response, status = module.handle_webhook("wh-1")
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "Bad Request"
|
||||
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow", side_effect=ValueError("missing"))
|
||||
def test_value_error_not_found(self, mock_get):
|
||||
with pytest.raises(NotFound):
|
||||
module.handle_webhook("wh-1")
|
||||
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow", side_effect=RequestEntityTooLarge())
|
||||
def test_request_entity_too_large(self, mock_get):
|
||||
with pytest.raises(RequestEntityTooLarge):
|
||||
module.handle_webhook("wh-1")
|
||||
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow", side_effect=Exception("boom"))
|
||||
def test_internal_error(self, mock_get):
|
||||
response, status = module.handle_webhook("wh-1")
|
||||
|
||||
assert status == 500
|
||||
assert response["error"] == "Internal server error"
|
||||
|
||||
|
||||
class TestHandleWebhookDebug:
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow")
|
||||
@patch.object(module.WebhookService, "extract_and_validate_webhook_data")
|
||||
@patch.object(module.WebhookService, "build_workflow_inputs", return_value={"x": 1})
|
||||
@patch.object(module.TriggerDebugEventBus, "dispatch")
|
||||
@patch.object(module.WebhookService, "generate_webhook_response")
|
||||
def test_debug_success(
|
||||
self,
|
||||
mock_generate,
|
||||
mock_dispatch,
|
||||
mock_build_inputs,
|
||||
mock_extract,
|
||||
mock_get,
|
||||
):
|
||||
mock_get.return_value = (DummyWebhookTrigger(), None, "node_config")
|
||||
mock_extract.return_value = {"method": "POST"}
|
||||
mock_generate.return_value = ({"ok": True}, 200)
|
||||
|
||||
response, status = module.handle_webhook_debug("wh-1")
|
||||
|
||||
assert status == 200
|
||||
assert response["ok"] is True
|
||||
mock_dispatch.assert_called_once()
|
||||
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow")
|
||||
@patch.object(module.WebhookService, "extract_and_validate_webhook_data", side_effect=ValueError("bad"))
|
||||
def test_debug_bad_request(self, mock_extract, mock_get):
|
||||
mock_get.return_value = (DummyWebhookTrigger(), None, "node_config")
|
||||
|
||||
response, status = module.handle_webhook_debug("wh-1")
|
||||
|
||||
assert status == 400
|
||||
assert response["error"] == "Bad Request"
|
||||
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow", side_effect=ValueError("missing"))
|
||||
def test_debug_not_found(self, mock_get):
|
||||
with pytest.raises(NotFound):
|
||||
module.handle_webhook_debug("wh-1")
|
||||
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow", side_effect=RequestEntityTooLarge())
|
||||
def test_debug_request_entity_too_large(self, mock_get):
|
||||
with pytest.raises(RequestEntityTooLarge):
|
||||
module.handle_webhook_debug("wh-1")
|
||||
|
||||
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow", side_effect=Exception("boom"))
|
||||
def test_debug_internal_error(self, mock_get):
|
||||
response, status = module.handle_webhook_debug("wh-1")
|
||||
|
||||
assert status == 500
|
||||
assert response["error"] == "Internal server error"
|
||||
Loading…
Reference in New Issue
Block a user