mirror of
https://github.com/langgenius/dify.git
synced 2026-08-31 01:36:38 +08:00
test: migrate controller identity ORM models to SQLite (#40626)
This commit is contained in:
@@ -14,7 +14,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from controllers.console.app import app_import as app_import_module
|
||||
from enums import DeploymentEdition
|
||||
from models.account import Account
|
||||
from models.account import Account, Tenant
|
||||
from models.base import TypeBase
|
||||
from models.engine import db
|
||||
from models.model import App, AppMode
|
||||
@@ -58,7 +58,9 @@ def _install_features(monkeypatch: pytest.MonkeyPatch, enabled: bool) -> None:
|
||||
def _make_account(account_id: str = "u1") -> Account:
|
||||
account = Account(name="Test User", email="test@example.com")
|
||||
account.id = account_id
|
||||
account._current_tenant = MagicMock(id="tenant-1")
|
||||
tenant = Tenant(name="Test Tenant")
|
||||
tenant.id = "tenant-1"
|
||||
account._current_tenant = tenant
|
||||
return account
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Test authentication security to prevent user enumeration."""
|
||||
|
||||
import base64
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
@@ -11,6 +11,7 @@ import services.errors.account
|
||||
from controllers.console.auth.error import AuthenticationFailedError
|
||||
from controllers.console.auth.login import LoginApi
|
||||
from enums import DeploymentEdition
|
||||
from models.account import Account
|
||||
|
||||
|
||||
def encode_password(password: str) -> str:
|
||||
@@ -135,7 +136,7 @@ class TestAuthenticationSecurity:
|
||||
# Mock the setup check
|
||||
|
||||
# Test with existing account
|
||||
mock_get_user.return_value = MagicMock(email="existing@example.com")
|
||||
mock_get_user.return_value = Account(name="Existing User", email="existing@example.com")
|
||||
mock_send_email.return_value = "token123"
|
||||
|
||||
with self.app.test_request_context("/reset-password", method="POST", json={"email": "existing@example.com"}):
|
||||
|
||||
@@ -34,7 +34,19 @@ from controllers.inner_api.plugin.plugin import (
|
||||
PluginUploadFileRequestApi,
|
||||
)
|
||||
from core.workflow.file_reference import build_file_reference
|
||||
from models import Tenant
|
||||
from models import Account, Tenant
|
||||
|
||||
|
||||
def _tenant() -> Tenant:
|
||||
tenant = Tenant(name="Test Tenant")
|
||||
tenant.id = "tenant-id"
|
||||
return tenant
|
||||
|
||||
|
||||
def _user() -> Account:
|
||||
user = Account(name="Test User", email="user@example.com")
|
||||
user.id = "user-id"
|
||||
return user
|
||||
|
||||
|
||||
def _extract_raw_post(cls):
|
||||
@@ -210,16 +222,16 @@ class TestPluginInvokeEncryptApi:
|
||||
"""Test that post() delegates to PluginEncrypter and returns model_dump output"""
|
||||
# Arrange
|
||||
mock_encrypter.invoke_encrypt.return_value = {"encrypted": "data"}
|
||||
mock_tenant = MagicMock()
|
||||
mock_user = MagicMock()
|
||||
tenant = _tenant()
|
||||
user = _user()
|
||||
mock_payload = MagicMock()
|
||||
|
||||
# Act — extract raw post() bypassing all decorators including plugin_data
|
||||
raw_post = _extract_raw_post(PluginInvokeEncryptApi)
|
||||
result = raw_post(api_instance, user_model=mock_user, tenant_model=mock_tenant, payload=mock_payload)
|
||||
result = raw_post(api_instance, user_model=user, tenant_model=tenant, payload=mock_payload)
|
||||
|
||||
# Assert
|
||||
mock_encrypter.invoke_encrypt.assert_called_once_with(mock_tenant, mock_payload)
|
||||
mock_encrypter.invoke_encrypt.assert_called_once_with(tenant, mock_payload)
|
||||
assert result["data"] == {"encrypted": "data"}
|
||||
assert result.get("error") == ""
|
||||
|
||||
@@ -228,13 +240,13 @@ class TestPluginInvokeEncryptApi:
|
||||
"""Test that post() catches exceptions and returns error response"""
|
||||
# Arrange
|
||||
mock_encrypter.invoke_encrypt.side_effect = RuntimeError("encrypt failed")
|
||||
mock_tenant = MagicMock()
|
||||
mock_user = MagicMock()
|
||||
tenant = _tenant()
|
||||
user = _user()
|
||||
mock_payload = MagicMock()
|
||||
|
||||
# Act
|
||||
raw_post = _extract_raw_post(PluginInvokeEncryptApi)
|
||||
result = raw_post(api_instance, user_model=mock_user, tenant_model=mock_tenant, payload=mock_payload)
|
||||
result = raw_post(api_instance, user_model=user, tenant_model=tenant, payload=mock_payload)
|
||||
|
||||
# Assert
|
||||
assert "encrypt failed" in result["error"]
|
||||
@@ -269,10 +281,8 @@ class TestPluginUploadFileRequestApi:
|
||||
# Arrange
|
||||
mock_get_uri.return_value = "/files/upload/for-plugin?sign=1"
|
||||
monkeypatch.setattr(plugin_module.dify_config, "INTERNAL_FILES_URL", "http://api:5001")
|
||||
mock_tenant = MagicMock()
|
||||
mock_tenant.id = "tenant-id"
|
||||
mock_user = MagicMock()
|
||||
mock_user.id = "user-id"
|
||||
tenant = _tenant()
|
||||
user = _user()
|
||||
mock_payload = MagicMock()
|
||||
mock_payload.filename = "test.pdf"
|
||||
mock_payload.mimetype = "application/pdf"
|
||||
@@ -280,7 +290,7 @@ class TestPluginUploadFileRequestApi:
|
||||
|
||||
# Act
|
||||
raw_post = _extract_raw_post(PluginUploadFileRequestApi)
|
||||
result = raw_post(api_instance, user_model=mock_user, tenant_model=mock_tenant, payload=mock_payload)
|
||||
result = raw_post(api_instance, user_model=user, tenant_model=tenant, payload=mock_payload)
|
||||
|
||||
# Assert
|
||||
mock_get_uri.assert_called_once_with(
|
||||
@@ -388,15 +398,14 @@ class TestPluginFetchAppInfoApi:
|
||||
"""Test that post() fetches app info and returns it"""
|
||||
# Arrange
|
||||
mock_invocation.fetch_app_info.return_value = {"app_name": "My App", "mode": "chat"}
|
||||
mock_tenant = MagicMock()
|
||||
mock_tenant.id = "tenant-id"
|
||||
mock_user = MagicMock()
|
||||
tenant = _tenant()
|
||||
user = _user()
|
||||
mock_payload = MagicMock()
|
||||
mock_payload.app_id = "app-123"
|
||||
|
||||
# Act
|
||||
raw_post = _extract_raw_post(PluginFetchAppInfoApi)
|
||||
result = raw_post(api_instance, user_model=mock_user, tenant_model=mock_tenant, payload=mock_payload)
|
||||
result = raw_post(api_instance, user_model=user, tenant_model=tenant, payload=mock_payload)
|
||||
|
||||
# Assert
|
||||
mock_invocation.fetch_app_info.assert_called_once_with("app-123", "tenant-id")
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import types
|
||||
from collections.abc import Iterator
|
||||
from inspect import unwrap
|
||||
@@ -14,7 +15,9 @@ from pydantic import ValidationError
|
||||
|
||||
import controllers.mcp.mcp as module
|
||||
from models.engine import db
|
||||
from models.model import EndUser
|
||||
from models.enums import EndUserType
|
||||
from models.model import App, AppAnnotationSetting, AppMCPServer, AppModelConfig, EndUser, IconType
|
||||
from models.workflow import Workflow, WorkflowType
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -24,7 +27,15 @@ def app() -> Iterator[Flask]:
|
||||
db.init_app(app)
|
||||
|
||||
with app.app_context():
|
||||
EndUser.__table__.create(db.engine)
|
||||
for table in (
|
||||
App.__table__,
|
||||
AppAnnotationSetting.__table__,
|
||||
AppMCPServer.__table__,
|
||||
AppModelConfig.__table__,
|
||||
EndUser.__table__,
|
||||
Workflow.__table__,
|
||||
):
|
||||
table.create(db.engine)
|
||||
yield app
|
||||
|
||||
|
||||
@@ -51,34 +62,79 @@ _APP_ID = str(uuid4())
|
||||
_SERVER_ID = str(uuid4())
|
||||
|
||||
|
||||
class DummyServer:
|
||||
def __init__(self, status, app_id=_APP_ID, tenant_id=_TENANT_ID, server_id=_SERVER_ID):
|
||||
self.status = status
|
||||
self.app_id = app_id
|
||||
self.tenant_id = tenant_id
|
||||
self.id = server_id
|
||||
self.description = "Test server"
|
||||
self.parameters_dict = {}
|
||||
def _server(status: module.AppMCPServerStatus | str) -> AppMCPServer:
|
||||
server = AppMCPServer(
|
||||
tenant_id=_TENANT_ID,
|
||||
app_id=_APP_ID,
|
||||
name="Test server",
|
||||
description="Test server",
|
||||
server_code="server-1",
|
||||
status=status,
|
||||
parameters="{}",
|
||||
)
|
||||
server.id = _SERVER_ID
|
||||
return server
|
||||
|
||||
|
||||
class DummyApp:
|
||||
def __init__(self, mode, workflow=None, app_model_config=None):
|
||||
self.id = _APP_ID
|
||||
self.tenant_id = _TENANT_ID
|
||||
self.name = "test_app"
|
||||
self.mode = mode
|
||||
self.workflow = workflow
|
||||
self.app_model_config = app_model_config
|
||||
def _app(
|
||||
mode: module.AppMode,
|
||||
*,
|
||||
workflow_variables: list[dict[str, object]] | None = None,
|
||||
with_model_config: bool = False,
|
||||
) -> App:
|
||||
app = App(
|
||||
id=_APP_ID,
|
||||
tenant_id=_TENANT_ID,
|
||||
name="test_app",
|
||||
description="",
|
||||
mode=mode,
|
||||
icon_type=IconType.EMOJI,
|
||||
icon="robot",
|
||||
icon_background="#ffffff",
|
||||
enable_site=False,
|
||||
enable_api=False,
|
||||
api_rpm=0,
|
||||
api_rph=0,
|
||||
is_demo=False,
|
||||
is_public=False,
|
||||
is_universal=False,
|
||||
max_active_requests=None,
|
||||
use_icon_as_answer_icon=False,
|
||||
)
|
||||
if workflow_variables is not None:
|
||||
workflow = Workflow.new(
|
||||
tenant_id=_TENANT_ID,
|
||||
app_id=_APP_ID,
|
||||
type=WorkflowType.WORKFLOW,
|
||||
version=Workflow.VERSION_DRAFT,
|
||||
graph=json.dumps({"nodes": [{"id": "start", "data": {"type": "start", "variables": workflow_variables}}]}),
|
||||
features="{}",
|
||||
created_by="user-1",
|
||||
environment_variables=[],
|
||||
conversation_variables=[],
|
||||
rag_pipeline_variables=[],
|
||||
)
|
||||
db.session.add(workflow)
|
||||
db.session.flush()
|
||||
app.workflow_id = workflow.id
|
||||
if with_model_config:
|
||||
config = AppModelConfig(app_id=_APP_ID)
|
||||
config.user_input_form = "[]"
|
||||
db.session.add(config)
|
||||
db.session.flush()
|
||||
app.app_model_config_id = config.id
|
||||
return app
|
||||
|
||||
|
||||
class DummyWorkflow:
|
||||
def user_input_form(self, to_old_structure=False):
|
||||
return []
|
||||
|
||||
|
||||
class DummyConfig:
|
||||
def to_dict(self):
|
||||
return {"user_input_form": []}
|
||||
def _end_user() -> EndUser:
|
||||
end_user = EndUser(
|
||||
tenant_id=_TENANT_ID,
|
||||
app_id=_APP_ID,
|
||||
type=EndUserType.MCP,
|
||||
session_id=_SERVER_ID,
|
||||
)
|
||||
end_user.id = str(uuid4())
|
||||
return end_user
|
||||
|
||||
|
||||
class DummyResult:
|
||||
@@ -103,11 +159,8 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(module.AppMode.ADVANCED_CHAT, workflow_variables=[])
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
@@ -127,11 +180,8 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(module.AppMode.ADVANCED_CHAT, workflow_variables=[])
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
@@ -150,11 +200,8 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(module.AppMode.ADVANCED_CHAT, workflow_variables=[])
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
@@ -174,11 +221,8 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status="inactive")
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
server = _server("inactive")
|
||||
app = _app(module.AppMode.ADVANCED_CHAT, workflow_variables=[])
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
@@ -206,11 +250,8 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.WORKFLOW,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(module.AppMode.WORKFLOW, workflow_variables=[])
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
@@ -287,11 +328,8 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=None, # No workflow
|
||||
)
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(module.AppMode.ADVANCED_CHAT)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
@@ -317,11 +355,8 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.CHAT,
|
||||
app_model_config=None, # No model config
|
||||
)
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(module.AppMode.CHAT)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
@@ -348,11 +383,8 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(module.AppMode.ADVANCED_CHAT, workflow_variables=[])
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
@@ -378,14 +410,10 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
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(),
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(
|
||||
module.AppMode.WORKFLOW,
|
||||
workflow_variables=[{"type": "text-input", "variable": "test_var", "label": "Test", "required": False}],
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
@@ -411,11 +439,8 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.CHAT,
|
||||
app_model_config=DummyConfig(),
|
||||
)
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(module.AppMode.CHAT, with_model_config=True)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
@@ -436,11 +461,8 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(
|
||||
mode=module.AppMode.ADVANCED_CHAT,
|
||||
workflow=DummyWorkflow(),
|
||||
)
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(module.AppMode.ADVANCED_CHAT, workflow_variables=[])
|
||||
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
@@ -454,7 +476,7 @@ class TestMCPAppApi:
|
||||
def test_validate_server_status_active(self):
|
||||
"""Test successful server status validation"""
|
||||
api = module.MCPAppApi()
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
|
||||
# Should not raise an exception
|
||||
api._validate_server_status(server)
|
||||
@@ -480,15 +502,10 @@ class TestMCPAppApi:
|
||||
}
|
||||
)
|
||||
|
||||
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(),
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(
|
||||
module.AppMode.WORKFLOW,
|
||||
workflow_variables=[{"type": "invalid-type", "variable": "test_var"}],
|
||||
)
|
||||
|
||||
api = module.MCPAppApi()
|
||||
@@ -543,11 +560,11 @@ class TestMCPProtocolVersionNegotiationApi:
|
||||
"""
|
||||
|
||||
def _make_api(self) -> module.MCPAppApi:
|
||||
server = DummyServer(status=module.AppMCPServerStatus.ACTIVE)
|
||||
app = DummyApp(mode=module.AppMode.CHAT, app_model_config=DummyConfig())
|
||||
server = _server(module.AppMCPServerStatus.ACTIVE)
|
||||
app = _app(module.AppMode.CHAT, with_model_config=True)
|
||||
api = module.MCPAppApi()
|
||||
api._get_mcp_server_and_app = MagicMock(return_value=(server, app))
|
||||
api._retrieve_end_user = MagicMock(return_value=MagicMock())
|
||||
api._retrieve_end_user = MagicMock(return_value=_end_user())
|
||||
return api
|
||||
|
||||
def _post(
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import uuid
|
||||
from collections.abc import Iterator
|
||||
from typing import cast
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import patch
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
@@ -14,6 +14,7 @@ from sqlalchemy import Engine
|
||||
from sqlalchemy.orm import Session
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from models.account import Account
|
||||
from models.engine import db
|
||||
from models.model import OAuthProviderApp
|
||||
from services.oauth_server import (
|
||||
@@ -178,7 +179,8 @@ class TestOAuthServerServiceTokenOperations:
|
||||
|
||||
def test_validate_access_token_loads_user_when_exists(self, mock_redis, sqlite_engine: Engine) -> None:
|
||||
mock_redis.get.return_value = b"user-88"
|
||||
expected_user = MagicMock()
|
||||
expected_user = Account(name="Test User", email="user@example.com")
|
||||
expected_user.id = "user-88"
|
||||
|
||||
with Session(sqlite_engine) as session:
|
||||
with patch("services.oauth_server.AccountService.load_user", return_value=expected_user) as mock_load:
|
||||
|
||||
Reference in New Issue
Block a user