This commit is contained in:
SATISH K C 2026-05-08 20:33:54 +00:00 committed by GitHub
commit 4af595f849
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 520 additions and 515 deletions

View File

@ -38,7 +38,7 @@ from libs.login import current_account_with_tenant, login_required
from models import App, DatasetPermissionEnum, Workflow
from models.model import IconType
from services.app_dsl_service import AppDslService
from services.app_service import AppService
from services.app_service import AppListParams, AppService, CreateAppParams
from services.enterprise.enterprise_service import EnterpriseService
from services.entities.dsl_entities import ImportMode, ImportStatus
from services.entities.knowledge_entities.knowledge_entities import (
@ -477,11 +477,18 @@ class AppListApi(Resource):
current_user, current_tenant_id = current_account_with_tenant()
args = AppListQuery.model_validate(_normalize_app_list_query_args(request.args))
args_dict = args.model_dump()
params = AppListParams(
page=args.page,
limit=args.limit,
mode=args.mode,
name=args.name,
tag_ids=args.tag_ids,
is_created_by_me=args.is_created_by_me,
)
# get app list
app_service = AppService()
app_pagination = app_service.get_paginate_apps(current_user.id, current_tenant_id, args_dict)
app_pagination = app_service.get_paginate_apps(current_user.id, current_tenant_id, params)
if not app_pagination:
empty = AppPagination(page=args.page, limit=args.limit, total=0, has_more=False, data=[])
return empty.model_dump(mode="json"), 200
@ -545,9 +552,17 @@ class AppListApi(Resource):
"""Create app"""
current_user, current_tenant_id = current_account_with_tenant()
args = CreateAppPayload.model_validate(console_ns.payload)
params = CreateAppParams(
name=args.name,
description=args.description,
mode=args.mode,
icon_type=args.icon_type,
icon=args.icon,
icon_background=args.icon_background,
)
app_service = AppService()
app = app_service.create_app(current_tenant_id, args.model_dump(), current_user)
app = app_service.create_app(current_tenant_id, params, current_user)
app_detail = AppDetail.model_validate(app, from_attributes=True)
return app_detail.model_dump(mode="json"), 201

View File

@ -1,9 +1,10 @@
import json
import logging
from typing import Any, TypedDict, cast
from typing import Any, Literal, TypedDict, cast
import sqlalchemy as sa
from flask_sqlalchemy.pagination import Pagination
from pydantic import BaseModel, Field
from sqlalchemy import select
from configs import dify_config
@ -31,39 +32,59 @@ from tasks.remove_app_and_related_data_task import remove_app_and_related_data_t
logger = logging.getLogger(__name__)
class AppListParams(BaseModel):
page: int = Field(default=1, ge=1)
limit: int = Field(default=20, ge=1, le=100)
mode: Literal["completion", "chat", "advanced-chat", "workflow", "agent-chat", "channel", "all"] = "all"
name: str | None = None
tag_ids: list[str] | None = None
is_created_by_me: bool | None = None
class CreateAppParams(BaseModel):
name: str = Field(min_length=1)
description: str | None = None
mode: Literal["chat", "agent-chat", "advanced-chat", "workflow", "completion"]
icon_type: str | None = None
icon: str | None = None
icon_background: str | None = None
api_rph: int = 0
api_rpm: int = 0
max_active_requests: int | None = None
class AppService:
def get_paginate_apps(self, user_id: str, tenant_id: str, args: dict[str, Any]) -> Pagination | None:
def get_paginate_apps(self, user_id: str, tenant_id: str, params: AppListParams) -> Pagination | None:
"""
Get app list with pagination
:param user_id: user id
:param tenant_id: tenant id
:param args: request args
:param params: query parameters
:return:
"""
filters = [App.tenant_id == tenant_id, App.is_universal == False]
if args["mode"] == "workflow":
if params.mode == "workflow":
filters.append(App.mode == AppMode.WORKFLOW)
elif args["mode"] == "completion":
elif params.mode == "completion":
filters.append(App.mode == AppMode.COMPLETION)
elif args["mode"] == "chat":
elif params.mode == "chat":
filters.append(App.mode == AppMode.CHAT)
elif args["mode"] == "advanced-chat":
elif params.mode == "advanced-chat":
filters.append(App.mode == AppMode.ADVANCED_CHAT)
elif args["mode"] == "agent-chat":
elif params.mode == "agent-chat":
filters.append(App.mode == AppMode.AGENT_CHAT)
if args.get("is_created_by_me", False):
if params.is_created_by_me:
filters.append(App.created_by == user_id)
if args.get("name"):
if params.name:
from libs.helper import escape_like_pattern
name = args["name"][:30]
name = params.name[:30]
escaped_name = escape_like_pattern(name)
filters.append(App.name.ilike(f"%{escaped_name}%", escape="\\"))
# Check if tag_ids is not empty to avoid WHERE false condition
if args.get("tag_ids") and len(args["tag_ids"]) > 0:
target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, args["tag_ids"])
if params.tag_ids and len(params.tag_ids) > 0:
target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, params.tag_ids)
if target_ids and len(target_ids) > 0:
filters.append(App.id.in_(target_ids))
else:
@ -71,21 +92,21 @@ class AppService:
app_models = db.paginate(
sa.select(App).where(*filters).order_by(App.created_at.desc()),
page=args["page"],
per_page=args["limit"],
page=params.page,
per_page=params.limit,
error_out=False,
)
return app_models
def create_app(self, tenant_id: str, args: dict[str, Any], account: Account) -> App:
def create_app(self, tenant_id: str, params: CreateAppParams, account: Account) -> App:
"""
Create app
:param tenant_id: tenant id
:param args: request args
:param params: app creation parameters
:param account: Account instance
"""
app_mode = AppMode.value_of(args["mode"])
app_mode = AppMode.value_of(params.mode)
app_template = default_app_templates[app_mode]
# get model config
@ -143,15 +164,16 @@ class AppService:
default_model_config["model"] = json.dumps(default_model_dict)
app = App(**app_template["app"])
app.name = args["name"]
app.description = args.get("description", "")
app.mode = args["mode"]
app.icon_type = args.get("icon_type", "emoji")
app.icon = args["icon"]
app.icon_background = args["icon_background"]
app.name = params.name
app.description = params.description or ""
app.mode = app_mode
app.icon_type = IconType(params.icon_type) if params.icon_type else IconType.EMOJI
app.icon = params.icon
app.icon_background = params.icon_background
app.tenant_id = tenant_id
app.api_rph = args.get("api_rph", 0)
app.api_rpm = args.get("api_rpm", 0)
app.api_rph = params.api_rph
app.api_rpm = params.api_rpm
app.max_active_requests = params.max_active_requests
app.created_by = account.id
app.updated_by = account.id

View File

@ -11,7 +11,7 @@ from models.enums import ConversationFromSource, MessageFileBelongsTo
from models.model import AppModelConfig, Conversation, EndUser, Message, MessageAgentThought
from services.account_service import AccountService, TenantService
from services.agent_service import AgentService
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
from tests.test_containers_integration_tests.helpers import generate_valid_password
@ -119,16 +119,16 @@ class TestAgentService:
tenant = account.current_tenant
# Create app with realistic data
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "agent-chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="agent-chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)

View File

@ -9,7 +9,7 @@ from models import Account
from models.enums import ConversationFromSource, InvokeFrom
from models.model import MessageAnnotation
from services.annotation_service import AppAnnotationService
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
from tests.test_containers_integration_tests.helpers import generate_valid_password
@ -86,16 +86,16 @@ class TestAnnotationService:
tenant = account.current_tenant
# Setup app creation arguments
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
# Create app
app_service = AppService()

View File

@ -37,7 +37,7 @@ from services.app_dsl_service import (
PendingData,
_check_version_compatibility,
)
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
from tests.test_containers_integration_tests.helpers import generate_valid_password
_DEFAULT_TENANT_ID = "00000000-0000-0000-0000-000000000001"
@ -147,16 +147,16 @@ class TestAppDslService:
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
return app, account

View File

@ -1,4 +1,5 @@
import uuid
from typing import Literal
from unittest.mock import ANY, MagicMock, patch
import pytest
@ -133,7 +134,10 @@ class TestAppGenerateService:
}
def _create_test_app_and_account(
self, db_session_with_containers: Session, mock_external_service_dependencies, mode="chat"
self,
db_session_with_containers: Session,
mock_external_service_dependencies,
mode: Literal["chat", "agent-chat", "advanced-chat", "workflow", "completion"] = "chat",
):
"""
Helper method to create a test app and account for testing.
@ -165,20 +169,20 @@ class TestAppGenerateService:
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
# Create app with realistic data
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": mode,
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
"max_active_requests": 5,
}
from services.app_service import AppService, CreateAppParams
from services.app_service import AppService
# Create app with realistic data
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode=mode,
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
max_active_requests=5,
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)

View File

@ -2,6 +2,7 @@ from unittest.mock import create_autospec, patch
import pytest
from faker import Faker
from pydantic import ValidationError
from sqlalchemy.orm import Session
from constants.model_template import default_app_templates
@ -11,7 +12,7 @@ from services.account_service import AccountService, TenantService
from tests.test_containers_integration_tests.helpers import generate_valid_password
# Delay import of AppService to avoid circular dependency
# from services.app_service import AppService
# from services.app_service import AppService, AppListParams, CreateAppParams
class TestAppService:
@ -63,34 +64,34 @@ class TestAppService:
tenant = account.current_tenant
# Setup app creation arguments
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
# Import here to avoid circular dependency
from services.app_service import AppService, CreateAppParams
app_params = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
# Create app
# Import here to avoid circular dependency
from services.app_service import AppService
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
app = app_service.create_app(tenant.id, app_params, account)
# Verify app was created correctly
assert app.name == app_args["name"]
assert app.description == app_args["description"]
assert app.mode == app_args["mode"]
assert app.icon_type == app_args["icon_type"]
assert app.icon == app_args["icon"]
assert app.icon_background == app_args["icon_background"]
assert app.name == app_params.name
assert app.description == app_params.description
assert app.mode == app_params.mode
assert app.icon_type == app_params.icon_type
assert app.icon == app_params.icon
assert app.icon_background == app_params.icon_background
assert app.tenant_id == tenant.id
assert app.api_rph == app_args["api_rph"]
assert app.api_rpm == app_args["api_rpm"]
assert app.api_rph == app_params.api_rph
assert app.api_rpm == app_params.api_rpm
assert app.created_by == account.id
assert app.updated_by == account.id
assert app.status == "normal"
@ -119,7 +120,7 @@ class TestAppService:
tenant = account.current_tenant
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_service = AppService()
@ -128,20 +129,20 @@ class TestAppService:
app_modes = [v.value for v in default_app_templates]
for mode in app_modes:
app_args = {
"name": f"{fake.company()} {mode}",
"description": f"Test app for {mode} mode",
"mode": mode,
"icon_type": "emoji",
"icon": "🚀",
"icon_background": "#4ECDC4",
}
app_params = CreateAppParams(
name=f"{fake.company()} {mode}",
description=f"Test app for {mode} mode",
mode=mode,
icon_type="emoji",
icon="🚀",
icon_background="#4ECDC4",
)
app = app_service.create_app(tenant.id, app_args, account)
app = app_service.create_app(tenant.id, app_params, account)
# Verify app mode was set correctly
assert app.mode == mode
assert app.name == app_args["name"]
assert app.name == app_params.name
assert app.tenant_id == tenant.id
assert app.created_by == account.id
@ -162,20 +163,20 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🎯",
"icon_background": "#45B7D1",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_params = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🎯",
icon_background="#45B7D1",
)
app_service = AppService()
created_app = app_service.create_app(tenant.id, app_args, account)
created_app = app_service.create_app(tenant.id, app_params, account)
# Get app using the service - needs current_user mock
mock_current_user = create_autospec(Account, instance=True)
@ -210,31 +211,27 @@ class TestAppService:
tenant = account.current_tenant
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppListParams, AppService, CreateAppParams
app_service = AppService()
# Create multiple apps
app_names = [fake.company() for _ in range(5)]
for name in app_names:
app_args = {
"name": name,
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "📱",
"icon_background": "#96CEB4",
}
app_service.create_app(tenant.id, app_args, account)
app_params = CreateAppParams(
name=name,
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="📱",
icon_background="#96CEB4",
)
app_service.create_app(tenant.id, app_params, account)
# Get paginated apps
args = {
"page": 1,
"limit": 10,
"mode": "chat",
}
params = AppListParams(page=1, limit=10, mode="chat")
paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, args)
paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, params)
# Verify pagination results
assert paginated_apps is not None
@ -266,60 +263,47 @@ class TestAppService:
tenant = account.current_tenant
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppListParams, AppService, CreateAppParams
app_service = AppService()
# Create apps with different modes
chat_app_args = {
"name": "Chat App",
"description": "A chat application",
"mode": "chat",
"icon_type": "emoji",
"icon": "💬",
"icon_background": "#FF6B6B",
}
completion_app_args = {
"name": "Completion App",
"description": "A completion application",
"mode": "completion",
"icon_type": "emoji",
"icon": "✍️",
"icon_background": "#4ECDC4",
}
chat_app_params = CreateAppParams(
name="Chat App",
description="A chat application",
mode="chat",
icon_type="emoji",
icon="💬",
icon_background="#FF6B6B",
)
completion_app_params = CreateAppParams(
name="Completion App",
description="A completion application",
mode="completion",
icon_type="emoji",
icon="✍️",
icon_background="#4ECDC4",
)
chat_app = app_service.create_app(tenant.id, chat_app_args, account)
completion_app = app_service.create_app(tenant.id, completion_app_args, account)
chat_app = app_service.create_app(tenant.id, chat_app_params, account)
completion_app = app_service.create_app(tenant.id, completion_app_params, account)
# Test filter by mode
chat_args = {
"page": 1,
"limit": 10,
"mode": "chat",
}
chat_apps = app_service.get_paginate_apps(account.id, tenant.id, chat_args)
chat_apps = app_service.get_paginate_apps(account.id, tenant.id, AppListParams(page=1, limit=10, mode="chat"))
assert len(chat_apps.items) == 1
assert chat_apps.items[0].mode == "chat"
# Test filter by name
name_args = {
"page": 1,
"limit": 10,
"mode": "chat",
"name": "Chat",
}
filtered_apps = app_service.get_paginate_apps(account.id, tenant.id, name_args)
filtered_apps = app_service.get_paginate_apps(
account.id, tenant.id, AppListParams(page=1, limit=10, mode="chat", name="Chat")
)
assert len(filtered_apps.items) == 1
assert "Chat" in filtered_apps.items[0].name
# Test filter by created_by_me
created_by_me_args = {
"page": 1,
"limit": 10,
"mode": "completion",
"is_created_by_me": True,
}
my_apps = app_service.get_paginate_apps(account.id, tenant.id, created_by_me_args)
my_apps = app_service.get_paginate_apps(
account.id, tenant.id, AppListParams(page=1, limit=10, mode="completion", is_created_by_me=True)
)
assert len(my_apps.items) == 1
def test_get_paginate_apps_with_tag_filters(
@ -341,34 +325,29 @@ class TestAppService:
tenant = account.current_tenant
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppListParams, AppService, CreateAppParams
app_service = AppService()
# Create an app
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🏷️",
"icon_background": "#FFEAA7",
}
app = app_service.create_app(tenant.id, app_args, account)
app_params = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🏷️",
icon_background="#FFEAA7",
)
app = app_service.create_app(tenant.id, app_params, account)
# Mock TagService to return the app ID for tag filtering
with patch("services.app_service.TagService.get_target_ids_by_tag_ids") as mock_tag_service:
mock_tag_service.return_value = [app.id]
# Test with tag filter
args = {
"page": 1,
"limit": 10,
"mode": "chat",
"tag_ids": ["tag1", "tag2"],
}
params = AppListParams(page=1, limit=10, mode="chat", tag_ids=["tag1", "tag2"])
paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, args)
paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, params)
# Verify tag service was called
mock_tag_service.assert_called_once_with("app", tenant.id, ["tag1", "tag2"])
@ -382,14 +361,9 @@ class TestAppService:
with patch("services.app_service.TagService.get_target_ids_by_tag_ids") as mock_tag_service:
mock_tag_service.return_value = []
args = {
"page": 1,
"limit": 10,
"mode": "chat",
"tag_ids": ["nonexistent_tag"],
}
params = AppListParams(page=1, limit=10, mode="chat", tag_ids=["nonexistent_tag"])
paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, args)
paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, params)
# Should return None when no apps match tag filter
assert paginated_apps is None
@ -411,20 +385,20 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🎯",
"icon_background": "#45B7D1",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_params = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🎯",
icon_background="#45B7D1",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
app = app_service.create_app(tenant.id, app_params, account)
# Store original values
original_name = app.name
@ -480,19 +454,19 @@ class TestAppService:
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_service = AppService()
app = app_service.create_app(
tenant.id,
{
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🎯",
"icon_background": "#45B7D1",
},
CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🎯",
icon_background="#45B7D1",
),
account,
)
@ -532,19 +506,19 @@ class TestAppService:
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_service = AppService()
app = app_service.create_app(
tenant.id,
{
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🎯",
"icon_background": "#45B7D1",
},
CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🎯",
icon_background="#45B7D1",
),
account,
)
@ -583,20 +557,20 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🎯",
"icon_background": "#45B7D1",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_params = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🎯",
icon_background="#45B7D1",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
app = app_service.create_app(tenant.id, app_params, account)
# Store original name
original_name = app.name
@ -636,20 +610,20 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🎯",
"icon_background": "#45B7D1",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_params = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🎯",
icon_background="#45B7D1",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
app = app_service.create_app(tenant.id, app_params, account)
# Store original values
original_icon = app.icon
@ -697,18 +671,17 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🌐",
"icon_background": "#74B9FF",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🌐",
icon_background="#74B9FF",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
@ -757,18 +730,17 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🔌",
"icon_background": "#A29BFE",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🔌",
icon_background="#A29BFE",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
@ -817,18 +789,17 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🔄",
"icon_background": "#FD79A8",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🔄",
icon_background="#FD79A8",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
@ -868,18 +839,17 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🗑️",
"icon_background": "#E17055",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🗑️",
icon_background="#E17055",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
@ -920,18 +890,17 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🧹",
"icon_background": "#00B894",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🧹",
icon_background="#00B894",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
@ -980,18 +949,17 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "📊",
"icon_background": "#6C5CE7",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="📊",
icon_background="#6C5CE7",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
@ -1019,18 +987,17 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🔗",
"icon_background": "#FDCB6E",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🔗",
icon_background="#FDCB6E",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
@ -1059,18 +1026,17 @@ class TestAppService:
tenant = account.current_tenant
# Create app first
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🆔",
"icon_background": "#E84393",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🆔",
icon_background="#E84393",
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
@ -1106,26 +1072,20 @@ class TestAppService:
password=generate_valid_password(fake),
)
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
# Setup app creation arguments with invalid mode
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "invalid_mode", # Invalid mode
"icon_type": "emoji",
"icon": "",
"icon_background": "#D63031",
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import CreateAppParams
app_service = AppService()
# Attempt to create app with invalid mode
with pytest.raises(ValueError, match="invalid mode value"):
app_service.create_app(tenant.id, app_args, account)
# Attempt to create app with invalid mode - Pydantic will reject invalid literal
with pytest.raises(ValidationError):
CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="invalid_mode", # type: ignore[arg-type]
icon_type="emoji",
icon="",
icon_background="#D63031",
)
def test_get_apps_with_special_characters_in_name(
self, db_session_with_containers: Session, mock_external_service_dependencies
@ -1151,99 +1111,103 @@ class TestAppService:
tenant = account.current_tenant
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppListParams, AppService, CreateAppParams
app_service = AppService()
# Create apps with special characters in names
app_with_percent = app_service.create_app(
tenant.id,
{
"name": "App with 50% discount",
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
},
CreateAppParams(
name="App with 50% discount",
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
),
account,
)
app_with_underscore = app_service.create_app(
tenant.id,
{
"name": "test_data_app",
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
},
CreateAppParams(
name="test_data_app",
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
),
account,
)
app_with_backslash = app_service.create_app(
tenant.id,
{
"name": "path\\to\\app",
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
},
CreateAppParams(
name="path\\to\\app",
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
),
account,
)
# Create app that should NOT match
app_no_match = app_service.create_app(
tenant.id,
{
"name": "100% different",
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
},
CreateAppParams(
name="100% different",
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
),
account,
)
# Test 1: Search with % character
args = {"name": "50%", "mode": "chat", "page": 1, "limit": 10}
paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, args)
paginated_apps = app_service.get_paginate_apps(
account.id, tenant.id, AppListParams(name="50%", mode="chat", page=1, limit=10)
)
assert paginated_apps is not None
assert paginated_apps.total == 1
assert len(paginated_apps.items) == 1
assert paginated_apps.items[0].name == "App with 50% discount"
# Test 2: Search with _ character
args = {"name": "test_data", "mode": "chat", "page": 1, "limit": 10}
paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, args)
paginated_apps = app_service.get_paginate_apps(
account.id, tenant.id, AppListParams(name="test_data", mode="chat", page=1, limit=10)
)
assert paginated_apps is not None
assert paginated_apps.total == 1
assert len(paginated_apps.items) == 1
assert paginated_apps.items[0].name == "test_data_app"
# Test 3: Search with \ character
args = {"name": "path\\to\\app", "mode": "chat", "page": 1, "limit": 10}
paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, args)
paginated_apps = app_service.get_paginate_apps(
account.id, tenant.id, AppListParams(name="path\\to\\app", mode="chat", page=1, limit=10)
)
assert paginated_apps is not None
assert paginated_apps.total == 1
assert len(paginated_apps.items) == 1
assert paginated_apps.items[0].name == "path\\to\\app"
# Test 4: Search with % should NOT match 100% (verifies escaping works)
args = {"name": "50%", "mode": "chat", "page": 1, "limit": 10}
paginated_apps = app_service.get_paginate_apps(account.id, tenant.id, args)
paginated_apps = app_service.get_paginate_apps(
account.id, tenant.id, AppListParams(name="50%", mode="chat", page=1, limit=10)
)
assert paginated_apps is not None
assert paginated_apps.total == 1
assert all("50%" in app.name for app in paginated_apps.items)
@ -1254,7 +1218,7 @@ class TestAppService:
"""Test get_app_code_by_id raises ValueError when site is missing."""
from uuid import uuid4
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
with pytest.raises(ValueError, match="not found"):
AppService.get_app_code_by_id(str(uuid4()))
@ -1263,7 +1227,7 @@ class TestAppService:
self, db_session_with_containers: Session, mock_external_service_dependencies
):
"""Test get_app_id_by_code raises ValueError when code does not exist."""
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
with pytest.raises(ValueError, match="not found"):
AppService.get_app_id_by_code("nonexistent-code")
@ -1274,7 +1238,7 @@ class TestAppService:
"""Test get_app_meta returns empty tool_icons when workflow is None."""
from types import SimpleNamespace
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_service = AppService()
workflow_app = SimpleNamespace(mode="workflow", workflow=None)
@ -1288,7 +1252,7 @@ class TestAppService:
"""Test get_app_meta returns empty tool_icons when app_model_config is None."""
from types import SimpleNamespace
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
app_service = AppService()
chat_app = SimpleNamespace(mode="chat", app_model_config=None)

View File

@ -6,7 +6,7 @@ from sqlalchemy.orm import Session
from models.enums import ConversationFromSource, FeedbackRating, InvokeFrom
from models.model import MessageFeedback
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
from services.errors.message import (
FirstMessageNotExistsError,
LastMessageNotExistsError,
@ -103,16 +103,16 @@ class TestMessageService:
tenant = account.current_tenant
# Setup app creation arguments
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "advanced-chat", # Use advanced-chat mode to use mocked workflow
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="advanced-chat", # Use advanced-chat mode to use mocked workflow,
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
# Create app
app_service = AppService()

View File

@ -11,7 +11,7 @@ from sqlalchemy.orm import Session
from core.ops.entities.config_entity import TracingProviderEnum
from models.model import TraceAppConfig
from services.account_service import AccountService, TenantService
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
from services.ops_service import OpsService
from tests.test_containers_integration_tests.helpers import generate_valid_password
@ -57,14 +57,14 @@ class TestOpsService:
app_service = AppService()
app = app_service.create_app(
tenant.id,
{
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
},
CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
),
account,
)
return app, account

View File

@ -8,7 +8,7 @@ from models import App, CreatorUserRole
from models.enums import ConversationFromSource
from models.model import EndUser, Message
from models.web import SavedMessage
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
from services.saved_message_service import SavedMessageService
from tests.test_containers_integration_tests.helpers import generate_valid_password
@ -73,16 +73,16 @@ class TestSavedMessageService:
tenant = account.current_tenant
# Create app with realistic data
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)

View File

@ -11,7 +11,7 @@ from models.enums import ConversationFromSource
from models.model import Conversation, EndUser
from models.web import PinnedConversation
from services.account_service import AccountService, TenantService
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
from services.web_conversation_service import WebConversationService
from tests.test_containers_integration_tests.helpers import generate_valid_password
@ -77,16 +77,16 @@ class TestWebConversationService:
tenant = account.current_tenant
# Create app with realistic data
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)

View File

@ -17,7 +17,7 @@ from models.workflow import WorkflowAppLogCreatedFrom
from services.account_service import AccountService, TenantService
# Delay import of AppService to avoid circular dependency
# from services.app_service import AppService
# from services.app_service import AppService, CreateAppParams
from services.workflow_app_service import LogView, WorkflowAppService
from tests.test_containers_integration_tests.helpers import generate_valid_password
@ -82,20 +82,20 @@ class TestWorkflowAppService:
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
tenant = account.current_tenant
# Create app with realistic data
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "workflow",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
# Create app with realistic data
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="workflow",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
@ -146,20 +146,20 @@ class TestWorkflowAppService:
"""
fake = Faker()
# Create app with realistic data
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "workflow",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
# Import here to avoid circular dependency
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
# Create app with realistic data
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="workflow",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)

View File

@ -13,7 +13,7 @@ from models.model import (
)
from models.workflow import WorkflowRun
from services.account_service import AccountService, TenantService
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
from services.workflow_run_service import WorkflowRunService
from tests.test_containers_integration_tests.helpers import generate_valid_password
@ -79,16 +79,16 @@ class TestWorkflowRunService:
tenant = account.current_tenant
# Create app with realistic data
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "chat",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="chat",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)
@ -535,13 +535,13 @@ class TestWorkflowRunService:
tenant = account.current_tenant
# Create app
app_args = {
"name": "Test App",
"mode": "chat",
"icon_type": "emoji",
"icon": "🚀",
"icon_background": "#4ECDC4",
}
app_args = CreateAppParams(
name="Test App",
mode="chat",
icon_type="emoji",
icon="🚀",
icon_background="#4ECDC4",
)
app = app_service.create_app(tenant.id, app_args, account)
# Create workflow run without node executions
@ -586,13 +586,13 @@ class TestWorkflowRunService:
tenant = account.current_tenant
# Create app
app_args = {
"name": "Test App",
"mode": "chat",
"icon_type": "emoji",
"icon": "🚀",
"icon_background": "#4ECDC4",
}
app_args = CreateAppParams(
name="Test App",
mode="chat",
icon_type="emoji",
icon="🚀",
icon_background="#4ECDC4",
)
app = app_service.create_app(tenant.id, app_args, account)
# Use invalid workflow run ID
@ -637,13 +637,13 @@ class TestWorkflowRunService:
tenant = account.current_tenant
# Create app
app_args = {
"name": "Test App",
"mode": "chat",
"icon_type": "emoji",
"icon": "🚀",
"icon_background": "#4ECDC4",
}
app_args = CreateAppParams(
name="Test App",
mode="chat",
icon_type="emoji",
icon="🚀",
icon_background="#4ECDC4",
)
app = app_service.create_app(tenant.id, app_args, account)
# Create workflow run

View File

@ -11,7 +11,7 @@ from core.tools.errors import WorkflowToolHumanInputNotSupportedError
from models.tools import WorkflowToolProvider
from models.workflow import Workflow as WorkflowModel
from services.account_service import AccountService, TenantService
from services.app_service import AppService
from services.app_service import AppService, CreateAppParams
from services.tools.workflow_tools_manage_service import WorkflowToolManageService
from tests.test_containers_integration_tests.helpers import generate_valid_password
@ -94,16 +94,16 @@ class TestWorkflowToolManageService:
tenant = account.current_tenant
# Create app with realistic data
app_args = {
"name": fake.company(),
"description": fake.text(max_nb_chars=100),
"mode": "workflow",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FF6B6B",
"api_rph": 100,
"api_rpm": 10,
}
app_args = CreateAppParams(
name=fake.company(),
description=fake.text(max_nb_chars=100),
mode="workflow",
icon_type="emoji",
icon="🤖",
icon_background="#FF6B6B",
api_rph=100,
api_rpm=10,
)
app_service = AppService()
app = app_service.create_app(tenant.id, app_args, account)