mirror of
https://github.com/langgenius/dify.git
synced 2026-10-04 13:47:55 +08:00
refactor: replace manual model_validate with @model_validate in explore/snippets controllers (#40241)
This commit is contained in:
1 parent
a1577fc48c
commit
925ca49f21
9 files changed
+437
-129
No files matched your search
@@ -1,13 +1,13 @@
|
||||
from datetime import datetime
|
||||
from typing import cast
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Namespace, Resource
|
||||
from pydantic import BaseModel, Field, RootModel, field_validator
|
||||
from sqlalchemy import select
|
||||
|
||||
from controllers.common.schema import query_params_from_model, register_response_schema_models
|
||||
from controllers.console import api
|
||||
from controllers.console.wraps import model_validate
|
||||
from extensions.ext_database import db
|
||||
from fields.base import ResponseModel
|
||||
from libs.helper import dump_response
|
||||
@@ -64,23 +64,22 @@ class BannerApi(Resource):
|
||||
|
||||
@api.doc(params=query_params_from_model(BannerListQuery))
|
||||
@api.response(200, "Success", api.models[BannerListResponse.__name__])
|
||||
def get(self):
|
||||
@model_validate(BannerListQuery)
|
||||
def get(self, req_data: BannerListQuery):
|
||||
"""Get banner list."""
|
||||
if not FeatureService.is_explore_banner_enabled():
|
||||
return dump_response(BannerListResponse, [])
|
||||
|
||||
query = BannerListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
# Build base query for enabled banners
|
||||
base_query = select(ExporleBanner).where(ExporleBanner.status == BannerStatus.ENABLED)
|
||||
|
||||
# Try to get banners in the requested language
|
||||
banners = db.session.scalars(
|
||||
base_query.where(ExporleBanner.language == query.language).order_by(ExporleBanner.sort)
|
||||
base_query.where(ExporleBanner.language == req_data.language).order_by(ExporleBanner.sort)
|
||||
).all()
|
||||
|
||||
# Fallback to en-US if no banners found and language is not en-US
|
||||
if not banners and query.language != "en-US":
|
||||
if not banners and req_data.language != "en-US":
|
||||
banners = db.session.scalars(
|
||||
base_query.where(ExporleBanner.language == "en-US").order_by(ExporleBanner.sort)
|
||||
).all()
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, Field, RootModel, computed_field, field_validator
|
||||
|
||||
from constants.languages import languages
|
||||
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.wraps import account_initialization_required, with_current_user
|
||||
from controllers.console.wraps import account_initialization_required, model_validate, with_current_user
|
||||
from extensions.ext_database import db
|
||||
from fields.base import ResponseModel
|
||||
from libs.helper import build_icon_url, dump_response
|
||||
@@ -114,10 +113,10 @@ class RecommendedAppListApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
def get(self, current_user: Account):
|
||||
@model_validate(RecommendedAppsQuery)
|
||||
def get(self, req_data: RecommendedAppsQuery, current_user: Account):
|
||||
# language args
|
||||
args = RecommendedAppsQuery.model_validate(request.args.to_dict(flat=True))
|
||||
language_prefix = _resolve_language(args.language, current_user)
|
||||
language_prefix = _resolve_language(req_data.language, current_user)
|
||||
|
||||
return dump_response(
|
||||
RecommendedAppListResponse,
|
||||
@@ -132,9 +131,9 @@ class LearnDifyAppListApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
def get(self, current_user: Account):
|
||||
args = RecommendedAppsQuery.model_validate(request.args.to_dict(flat=True))
|
||||
language_prefix = _resolve_language(args.language, current_user)
|
||||
@model_validate(RecommendedAppsQuery)
|
||||
def get(self, req_data: RecommendedAppsQuery, current_user: Account):
|
||||
language_prefix = _resolve_language(req_data.language, current_user)
|
||||
|
||||
return dump_response(
|
||||
LearnDifyAppListResponse,
|
||||
|
||||
@@ -49,7 +49,7 @@ from controllers.console.explore.error import (
|
||||
from controllers.console.explore.wraps import TrialAppResource
|
||||
from controllers.console.files import FILE_UPLOAD_PARAMS, upload_file_from_request
|
||||
from controllers.console.remote_files import RemoteFileUploadPayload, upload_remote_file_from_request
|
||||
from controllers.console.wraps import cloud_edition_billing_resource_check, with_current_user
|
||||
from controllers.console.wraps import cloud_edition_billing_resource_check, model_validate, with_current_user
|
||||
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
|
||||
from core.app.app_config.common.parameters_mapping import get_parameters_from_feature_dict
|
||||
from core.app.apps.base_app_queue_manager import AppQueueManager
|
||||
@@ -487,7 +487,8 @@ class TrialAppWorkflowRunApi(TrialAppResource):
|
||||
@console_ns.response(200, "Success")
|
||||
@with_current_user
|
||||
@with_session
|
||||
def post(self, session: Session, current_user: Account, trial_app):
|
||||
@model_validate(WorkflowRunRequest)
|
||||
def post(self, req_data: WorkflowRunRequest, session: Session, current_user: Account, trial_app):
|
||||
"""
|
||||
Run workflow
|
||||
"""
|
||||
@@ -498,8 +499,7 @@ class TrialAppWorkflowRunApi(TrialAppResource):
|
||||
if app_mode != AppMode.WORKFLOW:
|
||||
raise NotWorkflowAppError()
|
||||
|
||||
request_data = WorkflowRunRequest.model_validate(console_ns.payload)
|
||||
args = request_data.model_dump()
|
||||
args = req_data.model_dump()
|
||||
try:
|
||||
app_id = app_model.id
|
||||
user_id = current_user.id
|
||||
@@ -559,14 +559,14 @@ class TrialChatApi(TrialAppResource):
|
||||
@console_ns.response(200, "Success")
|
||||
@with_current_user
|
||||
@with_session
|
||||
def post(self, session: Session, current_user: Account, trial_app):
|
||||
@model_validate(ChatRequest)
|
||||
def post(self, req_data: ChatRequest, session: Session, current_user: Account, trial_app):
|
||||
app_model = trial_app
|
||||
app_mode = AppMode.value_of(app_model.mode)
|
||||
if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
|
||||
raise NotChatAppError()
|
||||
|
||||
request_data = ChatRequest.model_validate(console_ns.payload)
|
||||
args = request_data.model_dump()
|
||||
args = req_data.model_dump()
|
||||
|
||||
# Validate UUID values if provided
|
||||
if args.get("conversation_id"):
|
||||
@@ -709,14 +709,13 @@ class TrialChatTextApi(TrialAppResource):
|
||||
@console_ns.expect(console_ns.models[TextToSpeechRequest.__name__])
|
||||
@console_ns.response(200, "Success", console_ns.models[AudioBinaryResponse.__name__])
|
||||
@with_current_user
|
||||
def post(self, current_user: Account, trial_app):
|
||||
@model_validate(TextToSpeechRequest)
|
||||
def post(self, req_data: TextToSpeechRequest, current_user: Account, trial_app):
|
||||
app_model = trial_app
|
||||
try:
|
||||
request_data = TextToSpeechRequest.model_validate(console_ns.payload)
|
||||
|
||||
message_id = request_data.message_id
|
||||
text = request_data.text
|
||||
voice = request_data.voice
|
||||
message_id = req_data.message_id
|
||||
text = req_data.text
|
||||
voice = req_data.voice
|
||||
message_ref = None
|
||||
if message_id:
|
||||
app_ref = AppRefService.create_app_ref(app_model)
|
||||
@@ -770,13 +769,13 @@ class TrialCompletionApi(TrialAppResource):
|
||||
@console_ns.response(200, "Success")
|
||||
@with_current_user
|
||||
@with_session
|
||||
def post(self, session: Session, current_user: Account, trial_app):
|
||||
@model_validate(CompletionRequest)
|
||||
def post(self, req_data: CompletionRequest, session: Session, current_user: Account, trial_app):
|
||||
app_model = trial_app
|
||||
if app_model.mode != "completion":
|
||||
raise NotCompletionAppError()
|
||||
|
||||
request_data = CompletionRequest.model_validate(console_ns.payload)
|
||||
args = request_data.model_dump()
|
||||
args = req_data.model_dump()
|
||||
|
||||
streaming = args["response_mode"] == "streaming"
|
||||
args["auto_generate_name"] = False
|
||||
|
||||
@@ -36,6 +36,7 @@ from controllers.console.wraps import (
|
||||
RBACResourceScope,
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
rbac_permission_required,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
@@ -204,18 +205,18 @@ class SnippetDraftWorkflowApi(Resource):
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
||||
)
|
||||
def post(self, current_user: Account, snippet: CustomizedSnippet):
|
||||
@model_validate(SnippetDraftSyncPayload)
|
||||
def post(self, req_data: SnippetDraftSyncPayload, current_user: Account, snippet: CustomizedSnippet):
|
||||
"""Sync draft workflow for snippet."""
|
||||
payload = SnippetDraftSyncPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
try:
|
||||
snippet_service = _snippet_service()
|
||||
workflow = snippet_service.sync_draft_workflow(
|
||||
snippet=snippet,
|
||||
graph=payload.graph,
|
||||
unique_hash=payload.hash,
|
||||
graph=req_data.graph,
|
||||
unique_hash=req_data.hash,
|
||||
account=current_user,
|
||||
input_fields=payload.input_fields,
|
||||
input_fields=req_data.input_fields,
|
||||
)
|
||||
except WorkflowHashNotEqualError:
|
||||
raise DraftWorkflowNotSync()
|
||||
@@ -363,24 +364,24 @@ class SnippetPublishedAllWorkflowApi(Resource):
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
||||
)
|
||||
def get(self, snippet: CustomizedSnippet):
|
||||
@model_validate(SnippetWorkflowListQuery)
|
||||
def get(self, req_data: SnippetWorkflowListQuery, snippet: CustomizedSnippet):
|
||||
"""Get all published workflow versions for snippet."""
|
||||
args = SnippetWorkflowListQuery.model_validate(request.args.to_dict(flat=True))
|
||||
|
||||
snippet_service = _snippet_service()
|
||||
with Session(db.engine) as session:
|
||||
workflows, has_more = snippet_service.get_all_published_workflows(
|
||||
session=session,
|
||||
snippet=snippet,
|
||||
page=args.page,
|
||||
limit=args.limit,
|
||||
page=req_data.page,
|
||||
limit=req_data.limit,
|
||||
)
|
||||
|
||||
response = SnippetWorkflowPaginationResponse.model_validate(
|
||||
{
|
||||
"items": workflows,
|
||||
"page": args.page,
|
||||
"limit": args.limit,
|
||||
"page": req_data.page,
|
||||
"limit": req_data.limit,
|
||||
"has_more": has_more,
|
||||
},
|
||||
from_attributes=True,
|
||||
@@ -449,10 +450,16 @@ class SnippetWorkflowByIdApi(Resource):
|
||||
@rbac_permission_required(
|
||||
RBACResourceScope.WORKSPACE, RBACPermission.SNIPPETS_CREATE_AND_MODIFY, resource_required=False
|
||||
)
|
||||
def patch(self, current_user: Account, snippet: CustomizedSnippet, workflow_id: str):
|
||||
@model_validate(WorkflowUpdatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: WorkflowUpdatePayload,
|
||||
current_user: Account,
|
||||
snippet: CustomizedSnippet,
|
||||
workflow_id: str,
|
||||
):
|
||||
"""Update a published snippet workflow version's display metadata."""
|
||||
payload = WorkflowUpdatePayload.model_validate(console_ns.payload or {})
|
||||
update_data = payload.model_dump(exclude_unset=True)
|
||||
update_data = req_data.model_dump(exclude_unset=True)
|
||||
|
||||
if not update_data:
|
||||
return {"message": "No valid fields to update"}, 400
|
||||
@@ -575,16 +582,22 @@ class SnippetDraftNodeRunApi(Resource):
|
||||
@with_current_user
|
||||
@get_snippet
|
||||
@edit_permission_required
|
||||
def post(self, current_user: Account, snippet: CustomizedSnippet, node_id: str):
|
||||
@model_validate(SnippetDraftNodeRunPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: SnippetDraftNodeRunPayload,
|
||||
current_user: Account,
|
||||
snippet: CustomizedSnippet,
|
||||
node_id: str,
|
||||
):
|
||||
"""
|
||||
Run a single node in snippet draft workflow.
|
||||
|
||||
Executes a specific node with provided inputs for single-step debugging.
|
||||
Returns the node execution result including status, outputs, and timing.
|
||||
"""
|
||||
payload = SnippetDraftNodeRunPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
user_inputs = payload.inputs
|
||||
user_inputs = req_data.inputs
|
||||
|
||||
# Get draft workflow for file parsing
|
||||
snippet_service = _snippet_service()
|
||||
@@ -592,14 +605,14 @@ class SnippetDraftNodeRunApi(Resource):
|
||||
if not draft_workflow:
|
||||
raise NotFound("Draft workflow not found")
|
||||
|
||||
files = SnippetGenerateService.parse_files(draft_workflow, payload.files)
|
||||
files = SnippetGenerateService.parse_files(draft_workflow, req_data.files)
|
||||
|
||||
workflow_node_execution = SnippetGenerateService.run_draft_node(
|
||||
snippet=snippet,
|
||||
node_id=node_id,
|
||||
user_inputs=user_inputs,
|
||||
account=current_user,
|
||||
query=payload.query,
|
||||
query=req_data.query,
|
||||
files=files,
|
||||
session_maker=_snippet_session_maker(),
|
||||
)
|
||||
@@ -663,14 +676,21 @@ class SnippetDraftRunIterationNodeApi(Resource):
|
||||
@with_current_user
|
||||
@get_snippet
|
||||
@edit_permission_required
|
||||
def post(self, current_user: Account, snippet: CustomizedSnippet, node_id: str):
|
||||
@model_validate(SnippetIterationNodeRunPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: SnippetIterationNodeRunPayload,
|
||||
current_user: Account,
|
||||
snippet: CustomizedSnippet,
|
||||
node_id: str,
|
||||
):
|
||||
"""
|
||||
Run a draft workflow iteration node for snippet.
|
||||
|
||||
Iteration nodes execute their internal sub-graph multiple times over an input list.
|
||||
Returns an SSE event stream with iteration progress and results.
|
||||
"""
|
||||
args = SnippetIterationNodeRunPayload.model_validate(console_ns.payload or {}).model_dump(exclude_none=True)
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
|
||||
try:
|
||||
response = SnippetGenerateService.generate_single_iteration(
|
||||
@@ -708,21 +728,27 @@ class SnippetDraftRunLoopNodeApi(Resource):
|
||||
@with_current_user
|
||||
@get_snippet
|
||||
@edit_permission_required
|
||||
def post(self, current_user: Account, snippet: CustomizedSnippet, node_id: str):
|
||||
@model_validate(SnippetLoopNodeRunPayload)
|
||||
def post(
|
||||
self,
|
||||
req_data: SnippetLoopNodeRunPayload,
|
||||
current_user: Account,
|
||||
snippet: CustomizedSnippet,
|
||||
node_id: str,
|
||||
):
|
||||
"""
|
||||
Run a draft workflow loop node for snippet.
|
||||
|
||||
Loop nodes execute their internal sub-graph repeatedly until a condition is met.
|
||||
Returns an SSE event stream with loop progress and results.
|
||||
"""
|
||||
args = SnippetLoopNodeRunPayload.model_validate(console_ns.payload or {})
|
||||
|
||||
try:
|
||||
response = SnippetGenerateService.generate_single_loop(
|
||||
snippet=snippet,
|
||||
user=current_user,
|
||||
node_id=node_id,
|
||||
args=args,
|
||||
args=req_data,
|
||||
streaming=True,
|
||||
session_maker=_snippet_session_maker(),
|
||||
)
|
||||
@@ -751,15 +777,15 @@ class SnippetDraftWorkflowRunApi(Resource):
|
||||
@with_current_user
|
||||
@get_snippet
|
||||
@edit_permission_required
|
||||
def post(self, current_user: Account, snippet: CustomizedSnippet):
|
||||
@model_validate(SnippetDraftRunPayload)
|
||||
def post(self, req_data: SnippetDraftRunPayload, current_user: Account, snippet: CustomizedSnippet):
|
||||
"""
|
||||
Run draft workflow for snippet.
|
||||
|
||||
Executes the snippet's draft workflow with the provided inputs
|
||||
and returns an SSE event stream with execution progress and results.
|
||||
"""
|
||||
payload = SnippetDraftRunPayload.model_validate(console_ns.payload or {})
|
||||
args = payload.model_dump(exclude_none=True)
|
||||
args = req_data.model_dump(exclude_none=True)
|
||||
|
||||
try:
|
||||
response = SnippetGenerateService.generate(
|
||||
|
||||
@@ -36,6 +36,7 @@ from controllers.console.snippets.snippet_workflow import get_snippet
|
||||
from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
model_validate,
|
||||
setup_required,
|
||||
with_current_user,
|
||||
)
|
||||
@@ -186,9 +187,15 @@ class SnippetVariableApi(Resource):
|
||||
@console_ns.response(404, "Variable not found")
|
||||
@_snippet_draft_var_prerequisite
|
||||
@marshal_with(workflow_draft_variable_model)
|
||||
def patch(self, current_user: Account, snippet: CustomizedSnippet, variable_id: str) -> WorkflowDraftVariable:
|
||||
@model_validate(WorkflowDraftVariableUpdatePayload)
|
||||
def patch(
|
||||
self,
|
||||
req_data: WorkflowDraftVariableUpdatePayload,
|
||||
current_user: Account,
|
||||
snippet: CustomizedSnippet,
|
||||
variable_id: str,
|
||||
) -> WorkflowDraftVariable:
|
||||
draft_var_srv = WorkflowDraftVariableService(session=db.session())
|
||||
args_model = WorkflowDraftVariableUpdatePayload.model_validate(console_ns.payload or {})
|
||||
|
||||
variable = ensure_variable_access(
|
||||
variable=draft_var_srv.get_variable(variable_id=variable_id),
|
||||
@@ -198,8 +205,8 @@ class SnippetVariableApi(Resource):
|
||||
)
|
||||
_ensure_snippet_draft_variable_row_allowed(variable=variable, variable_id=variable_id)
|
||||
|
||||
new_name = args_model.name
|
||||
raw_value = args_model.value
|
||||
new_name = req_data.name
|
||||
raw_value = req_data.value
|
||||
if new_name is None and raw_value is None:
|
||||
return variable
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from flask import Flask
|
||||
from pydantic import ValidationError
|
||||
|
||||
import controllers.console.explore.recommended_app as module
|
||||
from controllers.console.explore.recommended_app import RecommendedAppsQuery
|
||||
from models import Account
|
||||
from models.model import AppMode, IconType
|
||||
|
||||
@@ -32,7 +33,7 @@ class TestRecommendedAppListApi:
|
||||
return_value=result_data,
|
||||
) as service_mock,
|
||||
):
|
||||
result = method(api, make_account("fr-FR"))
|
||||
result = method(api, RecommendedAppsQuery(language="en-US"), make_account("fr-FR"))
|
||||
|
||||
service_mock.assert_called_once_with("en-US", session=ANY)
|
||||
assert result == result_data
|
||||
@@ -51,7 +52,7 @@ class TestRecommendedAppListApi:
|
||||
return_value=result_data,
|
||||
) as service_mock,
|
||||
):
|
||||
result = method(api, make_account("fr-FR"))
|
||||
result = method(api, RecommendedAppsQuery(), make_account("fr-FR"))
|
||||
|
||||
service_mock.assert_called_once_with("fr-FR", session=ANY)
|
||||
assert result == result_data
|
||||
@@ -70,7 +71,7 @@ class TestRecommendedAppListApi:
|
||||
return_value=result_data,
|
||||
) as service_mock,
|
||||
):
|
||||
result = method(api, make_account(None))
|
||||
result = method(api, RecommendedAppsQuery(), make_account(None))
|
||||
|
||||
service_mock.assert_called_once_with(module.languages[0], session=ANY)
|
||||
assert result == result_data
|
||||
@@ -91,7 +92,7 @@ class TestLearnDifyAppListApi:
|
||||
return_value=result_data,
|
||||
) as service_mock,
|
||||
):
|
||||
result = method(api, make_account("fr-FR"))
|
||||
result = method(api, RecommendedAppsQuery(language="en-US"), make_account("fr-FR"))
|
||||
|
||||
service_mock.assert_called_once_with("en-US", session=ANY)
|
||||
assert result == result_data
|
||||
@@ -110,7 +111,7 @@ class TestLearnDifyAppListApi:
|
||||
return_value=result_data,
|
||||
) as service_mock,
|
||||
):
|
||||
result = method(api, make_account("fr-FR"))
|
||||
result = method(api, RecommendedAppsQuery(), make_account("fr-FR"))
|
||||
|
||||
service_mock.assert_called_once_with("fr-FR", session=ANY)
|
||||
assert result == result_data
|
||||
|
||||
@@ -8,7 +8,7 @@ from unittest.mock import MagicMock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from flask import Flask, request
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.orm import Session
|
||||
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
||||
@@ -28,6 +28,7 @@ from controllers.console.explore.error import (
|
||||
NotCompletionAppError,
|
||||
NotWorkflowAppError,
|
||||
)
|
||||
from controllers.console.explore.trial import ChatRequest, CompletionRequest, TextToSpeechRequest, WorkflowRunRequest
|
||||
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
|
||||
from core.errors.error import (
|
||||
ModelCurrentlyNotSupportError,
|
||||
@@ -258,9 +259,15 @@ class TestTrialAppWorkflowRunApi(_UsesSQLiteSession):
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
method = unwrap(api.post)
|
||||
|
||||
with app.test_request_context("/"):
|
||||
with app.test_request_context("/", json={"inputs": {}}):
|
||||
with pytest.raises(NotWorkflowAppError):
|
||||
method(api, self.sqlite_session, account, MagicMock(mode=AppMode.CHAT))
|
||||
method(
|
||||
api,
|
||||
WorkflowRunRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
MagicMock(mode=AppMode.CHAT),
|
||||
)
|
||||
|
||||
def test_success(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
@@ -271,7 +278,13 @@ class TestTrialAppWorkflowRunApi(_UsesSQLiteSession):
|
||||
patch.object(module.AppGenerateService, "generate", return_value=MagicMock()),
|
||||
patch.object(module.RecommendedAppService, "add_trial_app_record"),
|
||||
):
|
||||
result = method(api, self.sqlite_session, account, trial_app_workflow)
|
||||
result = method(
|
||||
api,
|
||||
WorkflowRunRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_workflow,
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
|
||||
@@ -288,7 +301,13 @@ class TestTrialAppWorkflowRunApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, self.sqlite_session, account, trial_app_workflow)
|
||||
method(
|
||||
api,
|
||||
WorkflowRunRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_workflow,
|
||||
)
|
||||
|
||||
def test_workflow_quota_exceeded(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
@@ -303,7 +322,13 @@ class TestTrialAppWorkflowRunApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, self.sqlite_session, account, trial_app_workflow)
|
||||
method(
|
||||
api,
|
||||
WorkflowRunRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_workflow,
|
||||
)
|
||||
|
||||
def test_workflow_model_not_support(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
@@ -318,7 +343,13 @@ class TestTrialAppWorkflowRunApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, self.sqlite_session, account, trial_app_workflow)
|
||||
method(
|
||||
api,
|
||||
WorkflowRunRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_workflow,
|
||||
)
|
||||
|
||||
def test_workflow_invoke_error(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
@@ -333,7 +364,13 @@ class TestTrialAppWorkflowRunApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, self.sqlite_session, account, trial_app_workflow)
|
||||
method(
|
||||
api,
|
||||
WorkflowRunRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_workflow,
|
||||
)
|
||||
|
||||
def test_workflow_rate_limit_error(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
@@ -348,7 +385,13 @@ class TestTrialAppWorkflowRunApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(InvokeRateLimitHttpError):
|
||||
method(api, self.sqlite_session, account, trial_app_workflow)
|
||||
method(
|
||||
api,
|
||||
WorkflowRunRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_workflow,
|
||||
)
|
||||
|
||||
def test_workflow_value_error(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
@@ -363,7 +406,13 @@ class TestTrialAppWorkflowRunApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, self.sqlite_session, account, trial_app_workflow)
|
||||
method(
|
||||
api,
|
||||
WorkflowRunRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_workflow,
|
||||
)
|
||||
|
||||
def test_workflow_generic_exception(self, app: Flask, trial_app_workflow: MagicMock, account: Account) -> None:
|
||||
api = module.TrialAppWorkflowRunApi()
|
||||
@@ -378,7 +427,13 @@ class TestTrialAppWorkflowRunApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
method(api, self.sqlite_session, account, trial_app_workflow)
|
||||
method(
|
||||
api,
|
||||
WorkflowRunRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_workflow,
|
||||
)
|
||||
|
||||
|
||||
class TestTrialChatApi(_UsesSQLiteSession):
|
||||
@@ -388,7 +443,13 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
|
||||
with app.test_request_context("/", json={"inputs": {}, "query": "hi"}):
|
||||
with pytest.raises(NotChatAppError):
|
||||
method(api, self.sqlite_session, account, MagicMock(mode="completion"))
|
||||
method(
|
||||
api,
|
||||
ChatRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
MagicMock(mode="completion"),
|
||||
)
|
||||
|
||||
def test_success(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatApi()
|
||||
@@ -399,7 +460,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
patch.object(module.AppGenerateService, "generate", return_value=MagicMock()),
|
||||
patch.object(module.RecommendedAppService, "add_trial_app_record"),
|
||||
):
|
||||
result = method(api, self.sqlite_session, account, trial_app_chat)
|
||||
result = method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
|
||||
@@ -416,7 +479,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(NotFound):
|
||||
method(api, self.sqlite_session, account, trial_app_chat)
|
||||
method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
def test_chat_conversation_completed(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatApi()
|
||||
@@ -431,7 +496,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ConversationCompletedError):
|
||||
method(api, self.sqlite_session, account, trial_app_chat)
|
||||
method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
def test_chat_app_config_broken(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatApi()
|
||||
@@ -446,7 +513,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(AppUnavailableError):
|
||||
method(api, self.sqlite_session, account, trial_app_chat)
|
||||
method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
def test_chat_provider_not_init(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatApi()
|
||||
@@ -461,7 +530,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, self.sqlite_session, account, trial_app_chat)
|
||||
method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
def test_chat_quota_exceeded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatApi()
|
||||
@@ -476,7 +547,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, self.sqlite_session, account, trial_app_chat)
|
||||
method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
def test_chat_model_not_support(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatApi()
|
||||
@@ -491,7 +564,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, self.sqlite_session, account, trial_app_chat)
|
||||
method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
def test_chat_invoke_error(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatApi()
|
||||
@@ -506,7 +581,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, self.sqlite_session, account, trial_app_chat)
|
||||
method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
def test_chat_rate_limit_error(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatApi()
|
||||
@@ -521,7 +598,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(InvokeRateLimitHttpError):
|
||||
method(api, self.sqlite_session, account, trial_app_chat)
|
||||
method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
def test_chat_value_error(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatApi()
|
||||
@@ -536,7 +615,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, self.sqlite_session, account, trial_app_chat)
|
||||
method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
def test_chat_generic_exception(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatApi()
|
||||
@@ -551,7 +632,9 @@ class TestTrialChatApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
method(api, self.sqlite_session, account, trial_app_chat)
|
||||
method(
|
||||
api, ChatRequest.model_validate(request.get_json()), self.sqlite_session, account, trial_app_chat
|
||||
)
|
||||
|
||||
|
||||
class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
@@ -561,7 +644,13 @@ class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
|
||||
with app.test_request_context("/", json={"inputs": {}, "query": ""}):
|
||||
with pytest.raises(NotCompletionAppError):
|
||||
method(api, self.sqlite_session, account, MagicMock(mode=AppMode.CHAT))
|
||||
method(
|
||||
api,
|
||||
CompletionRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
MagicMock(mode=AppMode.CHAT),
|
||||
)
|
||||
|
||||
def test_success(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
api = module.TrialCompletionApi()
|
||||
@@ -572,7 +661,13 @@ class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
patch.object(module.AppGenerateService, "generate", return_value=MagicMock()),
|
||||
patch.object(module.RecommendedAppService, "add_trial_app_record"),
|
||||
):
|
||||
result = method(api, self.sqlite_session, account, trial_app_completion)
|
||||
result = method(
|
||||
api,
|
||||
CompletionRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_completion,
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
|
||||
@@ -589,7 +684,13 @@ class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(AppUnavailableError):
|
||||
method(api, self.sqlite_session, account, trial_app_completion)
|
||||
method(
|
||||
api,
|
||||
CompletionRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_completion,
|
||||
)
|
||||
|
||||
def test_completion_provider_not_init(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
api = module.TrialCompletionApi()
|
||||
@@ -604,7 +705,13 @@ class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, self.sqlite_session, account, trial_app_completion)
|
||||
method(
|
||||
api,
|
||||
CompletionRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_completion,
|
||||
)
|
||||
|
||||
def test_completion_quota_exceeded(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
api = module.TrialCompletionApi()
|
||||
@@ -619,7 +726,13 @@ class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, self.sqlite_session, account, trial_app_completion)
|
||||
method(
|
||||
api,
|
||||
CompletionRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_completion,
|
||||
)
|
||||
|
||||
def test_completion_model_not_support(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
api = module.TrialCompletionApi()
|
||||
@@ -634,7 +747,13 @@ class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, self.sqlite_session, account, trial_app_completion)
|
||||
method(
|
||||
api,
|
||||
CompletionRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_completion,
|
||||
)
|
||||
|
||||
def test_completion_invoke_error(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
api = module.TrialCompletionApi()
|
||||
@@ -649,7 +768,13 @@ class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, self.sqlite_session, account, trial_app_completion)
|
||||
method(
|
||||
api,
|
||||
CompletionRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_completion,
|
||||
)
|
||||
|
||||
def test_completion_rate_limit_error(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
api = module.TrialCompletionApi()
|
||||
@@ -664,7 +789,13 @@ class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
method(api, self.sqlite_session, account, trial_app_completion)
|
||||
method(
|
||||
api,
|
||||
CompletionRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_completion,
|
||||
)
|
||||
|
||||
def test_completion_value_error(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
api = module.TrialCompletionApi()
|
||||
@@ -679,7 +810,13 @@ class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
method(api, self.sqlite_session, account, trial_app_completion)
|
||||
method(
|
||||
api,
|
||||
CompletionRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_completion,
|
||||
)
|
||||
|
||||
def test_completion_generic_exception(self, app: Flask, trial_app_completion: MagicMock, account: Account) -> None:
|
||||
api = module.TrialCompletionApi()
|
||||
@@ -694,7 +831,13 @@ class TestTrialCompletionApi(_UsesSQLiteSession):
|
||||
),
|
||||
):
|
||||
with pytest.raises(InternalServerError):
|
||||
method(api, self.sqlite_session, account, trial_app_completion)
|
||||
method(
|
||||
api,
|
||||
CompletionRequest.model_validate(request.get_json()),
|
||||
self.sqlite_session,
|
||||
account,
|
||||
trial_app_completion,
|
||||
)
|
||||
|
||||
|
||||
class TestTrialMessageSuggestedQuestionApi:
|
||||
@@ -843,7 +986,11 @@ class TestTrialChatAudioApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.AppUnavailableError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_no_audio_uploaded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatAudioApi()
|
||||
@@ -862,7 +1009,11 @@ class TestTrialChatAudioApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.NoAudioUploadedError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_missing_file_field_returns_400(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
"""A multipart POST with no `file` field must surface as 400, not 500.
|
||||
@@ -883,7 +1034,11 @@ class TestTrialChatAudioApi:
|
||||
patch.object(module.AudioService, "transcript_asr", side_effect=fake_asr),
|
||||
):
|
||||
with pytest.raises(module.NoAudioUploadedError) as exc_info:
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
assert exc_info.value.code == 400
|
||||
|
||||
@@ -904,7 +1059,11 @@ class TestTrialChatAudioApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.AudioTooLargeError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_unsupported_audio_type(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatAudioApi()
|
||||
@@ -923,7 +1082,11 @@ class TestTrialChatAudioApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.UnsupportedAudioTypeError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_provider_not_support_tts(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatAudioApi()
|
||||
@@ -942,7 +1105,11 @@ class TestTrialChatAudioApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.ProviderNotSupportSpeechToTextError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_speech_to_text_disabled(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatAudioApi()
|
||||
@@ -960,7 +1127,11 @@ class TestTrialChatAudioApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(SpeechToTextDisabledError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_provider_not_init(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatAudioApi()
|
||||
@@ -975,7 +1146,11 @@ class TestTrialChatAudioApi:
|
||||
patch.object(module.AudioService, "transcript_asr", side_effect=ProviderTokenNotInitError("test")),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_quota_exceeded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatAudioApi()
|
||||
@@ -990,7 +1165,11 @@ class TestTrialChatAudioApi:
|
||||
patch.object(module.AudioService, "transcript_asr", side_effect=QuotaExceededError()),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
|
||||
class TestTrialChatTextApi:
|
||||
@@ -1003,7 +1182,9 @@ class TestTrialChatTextApi:
|
||||
patch.object(module.AudioService, "transcript_tts", return_value={"audio": "base64_data"}),
|
||||
patch.object(module.RecommendedAppService, "add_trial_app_record"),
|
||||
):
|
||||
result = method(api, account, trial_app_chat)
|
||||
result = method(
|
||||
api, TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}), account, trial_app_chat
|
||||
)
|
||||
|
||||
assert result == {"audio": "base64_data"}
|
||||
|
||||
@@ -1018,7 +1199,9 @@ class TestTrialChatTextApi:
|
||||
patch.object(module.AudioService, "transcript_tts", transcript_tts),
|
||||
patch.object(module.RecommendedAppService, "add_trial_app_record"),
|
||||
):
|
||||
result = method(api, account, trial_app_chat)
|
||||
result = method(
|
||||
api, TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}), account, trial_app_chat
|
||||
)
|
||||
|
||||
assert result == {"audio": "base64_data"}
|
||||
assert transcript_tts.call_args.kwargs["message_ref"] == MessageRef(
|
||||
@@ -1040,7 +1223,12 @@ class TestTrialChatTextApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.AppUnavailableError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}),
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_provider_not_support(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatTextApi()
|
||||
@@ -1055,7 +1243,12 @@ class TestTrialChatTextApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.ProviderNotSupportSpeechToTextError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}),
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_audio_too_large(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatTextApi()
|
||||
@@ -1070,7 +1263,12 @@ class TestTrialChatTextApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.AudioTooLargeError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}),
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_no_audio_uploaded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatTextApi()
|
||||
@@ -1085,7 +1283,12 @@ class TestTrialChatTextApi:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.NoAudioUploadedError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}),
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_provider_not_init(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatTextApi()
|
||||
@@ -1096,7 +1299,12 @@ class TestTrialChatTextApi:
|
||||
patch.object(module.AudioService, "transcript_tts", side_effect=ProviderTokenNotInitError("test")),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}),
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_quota_exceeded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatTextApi()
|
||||
@@ -1107,7 +1315,12 @@ class TestTrialChatTextApi:
|
||||
patch.object(module.AudioService, "transcript_tts", side_effect=QuotaExceededError()),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}),
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_model_not_support(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatTextApi()
|
||||
@@ -1118,7 +1331,12 @@ class TestTrialChatTextApi:
|
||||
patch.object(module.AudioService, "transcript_tts", side_effect=ModelCurrentlyNotSupportError()),
|
||||
):
|
||||
with pytest.raises(ProviderModelCurrentlyNotSupportError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}),
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_invoke_error(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatTextApi()
|
||||
@@ -1129,14 +1347,19 @@ class TestTrialChatTextApi:
|
||||
patch.object(module.AudioService, "transcript_tts", side_effect=InvokeError("test error")),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}),
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
|
||||
class TestTrialAppWorkflowTaskStopApi:
|
||||
def test_not_workflow_app(self, app: Flask, trial_app_chat: MagicMock) -> None:
|
||||
api = module.TrialAppWorkflowTaskStopApi()
|
||||
|
||||
with app.test_request_context("/"):
|
||||
with app.test_request_context("/", json={"inputs": {}}):
|
||||
with pytest.raises(NotWorkflowAppError):
|
||||
api.post(trial_app_chat, str(uuid4()))
|
||||
|
||||
@@ -1336,7 +1559,11 @@ class TestTrialChatAudioApiExceptionHandlers:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderNotInitializeError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_quota_exceeded(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatAudioApi()
|
||||
@@ -1355,7 +1582,11 @@ class TestTrialChatAudioApiExceptionHandlers:
|
||||
),
|
||||
):
|
||||
with pytest.raises(ProviderQuotaExceededError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_invoke_error(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatAudioApi()
|
||||
@@ -1374,7 +1605,11 @@ class TestTrialChatAudioApiExceptionHandlers:
|
||||
),
|
||||
):
|
||||
with pytest.raises(CompletionRequestError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
|
||||
class TestTrialChatTextApiExceptionHandlers:
|
||||
@@ -1391,7 +1626,12 @@ class TestTrialChatTextApiExceptionHandlers:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.AppUnavailableError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}),
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
|
||||
def test_unsupported_audio_type(self, app: Flask, trial_app_chat: MagicMock, account: Account) -> None:
|
||||
api = module.TrialChatTextApi()
|
||||
@@ -1406,4 +1646,9 @@ class TestTrialChatTextApiExceptionHandlers:
|
||||
),
|
||||
):
|
||||
with pytest.raises(module.UnsupportedAudioTypeError):
|
||||
method(api, account, trial_app_chat)
|
||||
method(
|
||||
api,
|
||||
TextToSpeechRequest.model_validate(request.get_json(silent=True) or {}),
|
||||
account,
|
||||
trial_app_chat,
|
||||
)
|
||||
@@ -132,7 +132,14 @@ def test_draft_workflow_post_returns_400_for_invalid_graph(app: Flask, monkeypat
|
||||
method="POST",
|
||||
json={"graph": {"nodes": [], "edges": []}, "hash": "hash-1"},
|
||||
):
|
||||
response, status_code = handler(api, user, snippet)
|
||||
response, status_code = handler(
|
||||
api,
|
||||
snippet_workflow_module.SnippetDraftSyncPayload.model_validate(
|
||||
{"graph": {"nodes": [], "edges": []}, "hash": "hash-1"}
|
||||
),
|
||||
user,
|
||||
snippet,
|
||||
)
|
||||
|
||||
assert status_code == 400
|
||||
assert response == {"message": "invalid graph"}
|
||||
@@ -244,7 +251,11 @@ def test_list_published_snippet_workflows_includes_input_fields(
|
||||
handler = unwrap(api.get)
|
||||
|
||||
with app.test_request_context("/snippets/snippet-1/workflows?page=1&limit=20"):
|
||||
response = handler(api, snippet=snippet)
|
||||
response = handler(
|
||||
api,
|
||||
snippet_workflow_module.SnippetWorkflowListQuery.model_validate({"page": 1, "limit": 20}),
|
||||
snippet=snippet,
|
||||
)
|
||||
|
||||
assert response["items"][0]["input_fields"] == input_fields
|
||||
|
||||
@@ -411,7 +422,15 @@ def test_update_published_snippet_workflow_returns_updated_workflow(
|
||||
method="PATCH",
|
||||
json={"marked_name": "v1", "marked_comment": "first version"},
|
||||
):
|
||||
response = handler(api, user, snippet, workflow_id="workflow-1")
|
||||
response = handler(
|
||||
api,
|
||||
snippet_workflow_module.WorkflowUpdatePayload.model_validate(
|
||||
{"marked_name": "v1", "marked_comment": "first version"}
|
||||
),
|
||||
user,
|
||||
snippet,
|
||||
workflow_id="workflow-1",
|
||||
)
|
||||
|
||||
update_workflow.assert_called_once()
|
||||
update_call = update_workflow.call_args.kwargs
|
||||
@@ -432,7 +451,13 @@ def test_update_published_snippet_workflow_returns_400_when_no_fields(app: Flask
|
||||
handler = unwrap(api.patch)
|
||||
|
||||
with app.test_request_context("/snippets/snippet-1/workflows/workflow-1", method="PATCH", json={}):
|
||||
response, status_code = handler(api, _account("account-1"), _snippet(), workflow_id="workflow-1")
|
||||
response, status_code = handler(
|
||||
api,
|
||||
snippet_workflow_module.WorkflowUpdatePayload(),
|
||||
_account("account-1"),
|
||||
_snippet(),
|
||||
workflow_id="workflow-1",
|
||||
)
|
||||
|
||||
assert status_code == 400
|
||||
assert response == {"message": "No valid fields to update"}
|
||||
@@ -468,7 +493,13 @@ def test_update_published_snippet_workflow_raises_not_found(
|
||||
json={"marked_name": "v1"},
|
||||
):
|
||||
with pytest.raises(NotFound, match="Workflow not found"):
|
||||
handler(api, user, snippet, workflow_id="missing-workflow")
|
||||
handler(
|
||||
api,
|
||||
snippet_workflow_module.WorkflowUpdatePayload.model_validate({"marked_name": "v1"}),
|
||||
user,
|
||||
snippet,
|
||||
workflow_id="missing-workflow",
|
||||
)
|
||||
|
||||
sqlite_session.refresh(snippet)
|
||||
assert snippet.name == "Snippet"
|
||||
|
||||
+1
@@ -255,6 +255,7 @@ def test_variable_patch_returns_persisted_variable_without_committing_when_no_ch
|
||||
with app.test_request_context("/", method="PATCH", json={}):
|
||||
result = handler(
|
||||
api,
|
||||
module.WorkflowDraftVariableUpdatePayload(),
|
||||
_make_account(),
|
||||
snippet=SimpleNamespace(id="snippet-1", tenant_id="tenant-1"),
|
||||
variable_id="var-1",
|
||||
|
||||
Reference in new issue
Block a user