dify/api/tests/unit_tests/services/test_app_dsl_service.py
2026-07-15 06:48:28 +00:00

169 lines
5.9 KiB
Python

from types import SimpleNamespace
from typing import cast
from unittest.mock import Mock
import pytest
from sqlalchemy.orm import Session
from models import App, AppMode
from models.model import AppModelConfig, IconType
from services.app_dsl_service import AppDslService
from services.entities.dsl_entities import ImportStatus
@pytest.mark.parametrize("sqlite_session", [()], indirect=True)
def test_import_app_rejects_oversized_yaml_content_before_parsing(
monkeypatch: pytest.MonkeyPatch, sqlite_session: Session
) -> None:
monkeypatch.setattr("services.app_dsl_service.DSL_MAX_SIZE", 3)
service = AppDslService(session=sqlite_session)
account = Mock(current_tenant_id="tenant-1")
result = service.import_app(account=account, import_mode="yaml-content", yaml_content="你你")
assert result.status == ImportStatus.FAILED
assert result.error == "File size exceeds the limit of 10MB"
assert not sqlite_session.in_transaction()
@pytest.mark.parametrize("sqlite_session", [()], indirect=True)
def test_import_app_rejects_oversized_yaml_url_bytes_before_decode(
monkeypatch: pytest.MonkeyPatch, sqlite_session: Session
) -> None:
monkeypatch.setattr("services.app_dsl_service.DSL_MAX_SIZE", 1)
response = Mock()
response.raise_for_status.return_value = None
response.content = b"\xff\xff"
monkeypatch.setattr("services.app_dsl_service.remote_fetcher.make_request", Mock(return_value=response))
service = AppDslService(session=sqlite_session)
result = service.import_app(
account=Mock(current_tenant_id="tenant-1"),
import_mode="yaml-url",
yaml_url="https://example.com/app.yaml",
)
assert result.status == ImportStatus.FAILED
assert result.error == "File size exceeds the limit of 10MB"
assert not sqlite_session.in_transaction()
@pytest.mark.parametrize("sqlite_session", [()], indirect=True)
def test_import_app_returns_decode_error_for_invalid_yaml_url_bytes(
monkeypatch: pytest.MonkeyPatch, sqlite_session: Session
) -> None:
response = Mock()
response.raise_for_status.return_value = None
response.content = b"\xff"
monkeypatch.setattr("services.app_dsl_service.remote_fetcher.make_request", Mock(return_value=response))
service = AppDslService(session=sqlite_session)
result = service.import_app(
account=Mock(current_tenant_id="tenant-1"),
import_mode="yaml-url",
yaml_url="https://example.com/app.yaml",
)
assert result.status == ImportStatus.FAILED
assert "utf-8" in result.error
assert not sqlite_session.in_transaction()
def test_create_or_update_app_loads_existing_model_config_with_service_session() -> None:
session = Mock()
session.get.return_value = Mock()
service = AppDslService(session=session)
app = cast(
App,
SimpleNamespace(
id="app-1",
tenant_id="tenant-1",
app_model_config_id="config-1",
name="Existing app",
description="",
icon_type=IconType.EMOJI,
icon="robot",
icon_background="#FFFFFF",
),
)
result = service._create_or_update_app(
app=app,
data={"app": {"mode": AppMode.CHAT}, "model_config": {"model": {}}},
account=Mock(id="account-1"),
)
assert result is app
session.get.assert_called_once_with(AppModelConfig, "config-1")
def test_create_or_update_app_flushes_new_model_config_before_signal(monkeypatch: pytest.MonkeyPatch) -> None:
events: list[str] = []
session = Mock()
session.add.side_effect = lambda _config: events.append("add")
session.flush.side_effect = lambda: events.append("flush")
signal = Mock()
signal.send.side_effect = lambda *_args, **_kwargs: events.append("signal")
monkeypatch.setattr("services.app_dsl_service.app_model_config_was_updated", signal)
app = cast(
App,
SimpleNamespace(
id="app-1",
tenant_id="tenant-1",
app_model_config_id=None,
name="Existing app",
description="",
icon_type=IconType.EMOJI,
icon="robot",
icon_background="#FFFFFF",
),
)
AppDslService(session=session)._create_or_update_app(
app=app,
data={"app": {"mode": AppMode.CHAT}, "model_config": {"model": {}}},
account=Mock(id="account-1"),
)
assert events == ["add", "flush", "signal"]
assert signal.send.call_args.kwargs["session"] is session
session.commit.assert_not_called()
def test_export_dsl_loads_model_config_and_annotation_reply_with_request_session(
monkeypatch: pytest.MonkeyPatch,
) -> None:
model_config = {"model": {}, "agent_mode": {"tools": []}}
app_model_config = Mock(app_id="app-1")
app_model_config.to_dict.return_value = model_config
session = Mock()
session.get.return_value = app_model_config
annotation_reply = {"enabled": False}
load_annotation_reply_config = Mock(return_value=annotation_reply)
monkeypatch.setattr("services.app_dsl_service.load_annotation_reply_config", load_annotation_reply_config)
monkeypatch.setattr(
"services.app_dsl_service.DependenciesAnalysisService.generate_dependencies",
Mock(return_value=[]),
)
app = cast(
App,
SimpleNamespace(
id="app-1",
tenant_id="tenant-1",
app_model_config_id="config-1",
mode=AppMode.CHAT,
name="Chat app",
icon_type=IconType.EMOJI,
icon="robot",
icon_background="#FFFFFF",
description="",
use_icon_as_answer_icon=False,
),
)
AppDslService.export_dsl(app, session=session)
session.get.assert_called_once_with(AppModelConfig, "config-1")
load_annotation_reply_config.assert_called_once_with(session, "app-1")
app_model_config.to_dict.assert_called_once_with(annotation_reply=annotation_reply)