test: migrate controller identity ORM models to SQLite (#40626)

This commit is contained in:
Asuka Minato
2026-08-14 08:54:48 +00:00
committed by GitHub
parent 640bf56859
commit 0a1e52598f
5 changed files with 151 additions and 120 deletions
@@ -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")
+114 -97
View File
@@ -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: