mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 18:58:35 +08:00
560 lines
24 KiB
Python
560 lines
24 KiB
Python
"""Unit tests for recommended app orchestration and SQLite-backed trial state."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from typing import TypedDict, Unpack, cast
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from models.model import AccountTrialAppRecord, App, AppMode, TrialApp
|
|
from services import recommended_app_service as service_module
|
|
from services.feature_service import SystemFeatureModel
|
|
from services.recommended_app_service import RecommendedAppService
|
|
|
|
pytestmark = pytest.mark.parametrize(
|
|
"sqlite_session",
|
|
[(TrialApp, AccountTrialAppRecord, App)],
|
|
indirect=True,
|
|
)
|
|
|
|
|
|
class RecommendedAppPayload(TypedDict, total=False):
|
|
id: str
|
|
app_id: str
|
|
name: str
|
|
description: str
|
|
category: str
|
|
icon: str
|
|
model_config: object
|
|
workflows: list[str]
|
|
tools: list[str]
|
|
can_trial: bool
|
|
|
|
|
|
class AppsResponse(TypedDict):
|
|
recommended_apps: list[RecommendedAppPayload] | None
|
|
categories: list[str]
|
|
|
|
|
|
class AppDetailKwargs(TypedDict, total=False):
|
|
category: str
|
|
icon: str
|
|
model_config: object
|
|
workflows: list[str]
|
|
tools: list[str]
|
|
|
|
|
|
# ── Helpers ────────────────────────────────────────────────────────────
|
|
|
|
|
|
def _apps_response(
|
|
recommended_apps: list[RecommendedAppPayload] | None = None,
|
|
categories: list[str] | None = None,
|
|
) -> AppsResponse:
|
|
if recommended_apps is None:
|
|
recommended_apps = [
|
|
{"id": "app-1", "name": "Test App 1", "description": "d1", "category": "productivity"},
|
|
{"id": "app-2", "name": "Test App 2", "description": "d2", "category": "communication"},
|
|
]
|
|
if categories is None:
|
|
categories = ["productivity", "communication", "utilities"]
|
|
return {"recommended_apps": recommended_apps, "categories": categories}
|
|
|
|
|
|
def _app_detail(
|
|
app_id: str = "app-123",
|
|
name: str = "Test App",
|
|
description: str = "Test description",
|
|
**kwargs: Unpack[AppDetailKwargs],
|
|
) -> RecommendedAppPayload:
|
|
detail = RecommendedAppPayload(
|
|
id=app_id,
|
|
name=name,
|
|
description=description,
|
|
category=kwargs.get("category", "productivity"),
|
|
icon=kwargs.get("icon", "🚀"),
|
|
model_config=kwargs.get("model_config", {}),
|
|
)
|
|
detail.update(**kwargs)
|
|
return detail
|
|
|
|
|
|
def _mock_factory_for_apps(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
*,
|
|
mode: str,
|
|
result: AppsResponse,
|
|
fallback_result: AppsResponse | None = None,
|
|
) -> tuple[MagicMock, MagicMock]:
|
|
retrieval_instance = MagicMock()
|
|
retrieval_instance.get_recommended_apps_and_categories.return_value = result
|
|
retrieval_factory = MagicMock(return_value=retrieval_instance)
|
|
monkeypatch.setattr(service_module.dify_config, "HOSTED_FETCH_APP_TEMPLATES_MODE", mode, raising=False)
|
|
monkeypatch.setattr(
|
|
service_module.RecommendAppRetrievalFactory,
|
|
"get_recommend_app_factory",
|
|
MagicMock(return_value=retrieval_factory),
|
|
)
|
|
builtin_instance = MagicMock()
|
|
if fallback_result is not None:
|
|
builtin_instance.fetch_recommended_apps_from_builtin.return_value = fallback_result
|
|
monkeypatch.setattr(
|
|
service_module.RecommendAppRetrievalFactory,
|
|
"get_buildin_recommend_app_retrieval",
|
|
MagicMock(return_value=builtin_instance),
|
|
)
|
|
return retrieval_instance, builtin_instance
|
|
|
|
|
|
def _mock_factory_for_app_detail(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
*,
|
|
result: RecommendedAppPayload | None,
|
|
) -> MagicMock:
|
|
retrieval_instance = MagicMock()
|
|
retrieval_instance.get_recommend_app_detail.return_value = result
|
|
retrieval_factory = MagicMock(return_value=retrieval_instance)
|
|
monkeypatch.setattr(service_module.dify_config, "HOSTED_FETCH_APP_TEMPLATES_MODE", "remote", raising=False)
|
|
monkeypatch.setattr(
|
|
service_module.RecommendAppRetrievalFactory,
|
|
"get_recommend_app_factory",
|
|
MagicMock(return_value=retrieval_factory),
|
|
)
|
|
return retrieval_instance
|
|
|
|
|
|
def _persist_app(session: Session, *, name: str) -> App:
|
|
app = App(
|
|
tenant_id=str(uuid.uuid4()),
|
|
name=name,
|
|
mode=AppMode.CHAT,
|
|
enable_site=True,
|
|
enable_api=True,
|
|
)
|
|
app.id = str(uuid.uuid4())
|
|
session.add(app)
|
|
session.commit()
|
|
return app
|
|
|
|
|
|
# ── Pure logic tests: get_recommended_apps_and_categories ──────────────
|
|
|
|
|
|
class TestRecommendedAppServiceGetApps:
|
|
@patch("services.recommended_app_service.RecommendAppRetrievalFactory", autospec=True)
|
|
@patch("services.recommended_app_service.dify_config")
|
|
def test_success_with_apps(
|
|
self, mock_config: MagicMock, mock_factory_class: MagicMock, sqlite_session: Session
|
|
) -> None:
|
|
mock_config.HOSTED_FETCH_APP_TEMPLATES_MODE = "remote"
|
|
expected = _apps_response()
|
|
|
|
mock_instance = MagicMock()
|
|
mock_instance.get_recommended_apps_and_categories.return_value = expected
|
|
mock_factory = MagicMock(return_value=mock_instance)
|
|
mock_factory_class.get_recommend_app_factory.return_value = mock_factory
|
|
|
|
result = RecommendedAppService.get_recommended_apps_and_categories("en-US", session=sqlite_session)
|
|
|
|
assert result == expected
|
|
assert len(result["recommended_apps"]) == 2
|
|
assert len(result["categories"]) == 3
|
|
mock_factory_class.get_recommend_app_factory.assert_called_once_with("remote")
|
|
mock_instance.get_recommended_apps_and_categories.assert_called_once_with("en-US", session=sqlite_session)
|
|
|
|
@patch("services.recommended_app_service.RecommendAppRetrievalFactory", autospec=True)
|
|
@patch("services.recommended_app_service.dify_config")
|
|
def test_fallback_to_builtin_when_empty(
|
|
self, mock_config: MagicMock, mock_factory_class: MagicMock, sqlite_session: Session
|
|
) -> None:
|
|
mock_config.HOSTED_FETCH_APP_TEMPLATES_MODE = "remote"
|
|
empty_response = AppsResponse(recommended_apps=[], categories=[])
|
|
builtin_response = _apps_response(
|
|
recommended_apps=[{"id": "builtin-1", "name": "Builtin App", "category": "default"}]
|
|
)
|
|
|
|
mock_remote_instance = MagicMock()
|
|
mock_remote_instance.get_recommended_apps_and_categories.return_value = empty_response
|
|
mock_factory_class.get_recommend_app_factory.return_value = MagicMock(return_value=mock_remote_instance)
|
|
|
|
mock_builtin_instance = MagicMock()
|
|
mock_builtin_instance.fetch_recommended_apps_from_builtin.return_value = builtin_response
|
|
mock_factory_class.get_buildin_recommend_app_retrieval.return_value = mock_builtin_instance
|
|
|
|
result = RecommendedAppService.get_recommended_apps_and_categories("zh-CN", session=sqlite_session)
|
|
|
|
assert result == builtin_response
|
|
assert result["recommended_apps"][0]["id"] == "builtin-1"
|
|
mock_builtin_instance.fetch_recommended_apps_from_builtin.assert_called_once_with("en-US")
|
|
|
|
@patch("services.recommended_app_service.RecommendAppRetrievalFactory", autospec=True)
|
|
@patch("services.recommended_app_service.dify_config")
|
|
def test_fallback_when_none_recommended_apps(
|
|
self, mock_config: MagicMock, mock_factory_class: MagicMock, sqlite_session: Session
|
|
) -> None:
|
|
mock_config.HOSTED_FETCH_APP_TEMPLATES_MODE = "db"
|
|
none_response = AppsResponse(recommended_apps=None, categories=["test"])
|
|
builtin_response = _apps_response()
|
|
|
|
mock_db_instance = MagicMock()
|
|
mock_db_instance.get_recommended_apps_and_categories.return_value = none_response
|
|
mock_factory_class.get_recommend_app_factory.return_value = MagicMock(return_value=mock_db_instance)
|
|
|
|
mock_builtin_instance = MagicMock()
|
|
mock_builtin_instance.fetch_recommended_apps_from_builtin.return_value = builtin_response
|
|
mock_factory_class.get_buildin_recommend_app_retrieval.return_value = mock_builtin_instance
|
|
|
|
result = RecommendedAppService.get_recommended_apps_and_categories("en-US", session=sqlite_session)
|
|
|
|
assert result == builtin_response
|
|
mock_builtin_instance.fetch_recommended_apps_from_builtin.assert_called_once()
|
|
|
|
@patch("services.recommended_app_service.RecommendAppRetrievalFactory", autospec=True)
|
|
@patch("services.recommended_app_service.dify_config")
|
|
def test_different_languages(
|
|
self, mock_config: MagicMock, mock_factory_class: MagicMock, sqlite_session: Session
|
|
) -> None:
|
|
mock_config.HOSTED_FETCH_APP_TEMPLATES_MODE = "builtin"
|
|
|
|
for language in ["en-US", "zh-CN", "ja-JP", "fr-FR"]:
|
|
lang_response = _apps_response(
|
|
recommended_apps=[{"id": f"app-{language}", "name": f"App {language}", "category": "test"}]
|
|
)
|
|
mock_instance = MagicMock()
|
|
mock_instance.get_recommended_apps_and_categories.return_value = lang_response
|
|
mock_factory_class.get_recommend_app_factory.return_value = MagicMock(return_value=mock_instance)
|
|
|
|
result = RecommendedAppService.get_recommended_apps_and_categories(language, session=sqlite_session)
|
|
|
|
assert result["recommended_apps"][0]["id"] == f"app-{language}"
|
|
mock_instance.get_recommended_apps_and_categories.assert_called_with(language, session=sqlite_session)
|
|
|
|
@patch("services.recommended_app_service.RecommendAppRetrievalFactory", autospec=True)
|
|
@patch("services.recommended_app_service.dify_config")
|
|
def test_uses_correct_factory_mode(
|
|
self, mock_config: MagicMock, mock_factory_class: MagicMock, sqlite_session: Session
|
|
) -> None:
|
|
for mode in ["remote", "builtin", "db"]:
|
|
mock_config.HOSTED_FETCH_APP_TEMPLATES_MODE = mode
|
|
response = _apps_response()
|
|
mock_instance = MagicMock()
|
|
mock_instance.get_recommended_apps_and_categories.return_value = response
|
|
mock_factory_class.get_recommend_app_factory.return_value = MagicMock(return_value=mock_instance)
|
|
|
|
RecommendedAppService.get_recommended_apps_and_categories("en-US", session=sqlite_session)
|
|
|
|
mock_factory_class.get_recommend_app_factory.assert_called_with(mode)
|
|
|
|
|
|
# ── Database-backed tests: get_app ─────────────────────────────────────
|
|
|
|
|
|
class TestRecommendedAppServiceGetApp:
|
|
def test_returns_normal_recommended_app(self, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session) -> None:
|
|
app = _persist_app(sqlite_session, name="Recommended App")
|
|
|
|
retrieval_instance = _mock_factory_for_app_detail(
|
|
monkeypatch,
|
|
result=RecommendedAppPayload(id=app.id),
|
|
)
|
|
feature_lookup = MagicMock(side_effect=AssertionError("get_app must not inspect trial features"))
|
|
monkeypatch.setattr(service_module.FeatureService, "get_system_features", feature_lookup)
|
|
|
|
result = RecommendedAppService.get_app(app.id, session=sqlite_session)
|
|
|
|
assert result is app
|
|
retrieval_instance.get_recommend_app_detail.assert_called_once_with(app.id, session=sqlite_session)
|
|
feature_lookup.assert_not_called()
|
|
|
|
def test_returns_none_when_app_is_not_recommended(
|
|
self, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session
|
|
) -> None:
|
|
app = _persist_app(sqlite_session, name="Private App")
|
|
|
|
retrieval_instance = _mock_factory_for_app_detail(monkeypatch, result=None)
|
|
|
|
result = RecommendedAppService.get_app(app.id, session=sqlite_session)
|
|
|
|
assert result is None
|
|
retrieval_instance.get_recommend_app_detail.assert_called_once_with(app.id, session=sqlite_session)
|
|
|
|
|
|
# ── Pure logic tests: get_recommend_app_detail ─────────────────────────
|
|
|
|
|
|
class TestRecommendedAppServiceGetDetail:
|
|
@patch("services.recommended_app_service.FeatureService", autospec=True)
|
|
@patch("services.recommended_app_service.RecommendAppRetrievalFactory", autospec=True)
|
|
@patch("services.recommended_app_service.dify_config")
|
|
def test_returns_retrieval_detail_when_trial_disabled(
|
|
self,
|
|
mock_config: MagicMock,
|
|
mock_factory_class: MagicMock,
|
|
mock_feature_service: MagicMock,
|
|
sqlite_session: Session,
|
|
) -> None:
|
|
mock_config.HOSTED_FETCH_APP_TEMPLATES_MODE = "remote"
|
|
mock_feature_service.get_system_features.return_value = SystemFeatureModel(enable_trial_app=False)
|
|
cases: list[tuple[str, RecommendedAppPayload]] = [
|
|
(
|
|
"complex-app",
|
|
_app_detail(
|
|
app_id="complex-app",
|
|
name="Complex App",
|
|
model_config={
|
|
"provider": "openai",
|
|
"model": "gpt-4",
|
|
"parameters": {"temperature": 0.7, "max_tokens": 2000, "top_p": 1.0},
|
|
},
|
|
workflows=["workflow-1", "workflow-2"],
|
|
tools=["tool-1", "tool-2", "tool-3"],
|
|
),
|
|
),
|
|
("app-empty", RecommendedAppPayload()),
|
|
]
|
|
|
|
for app_id, expected in cases:
|
|
mock_instance = MagicMock()
|
|
mock_instance.get_recommend_app_detail.return_value = expected
|
|
mock_factory_class.get_recommend_app_factory.return_value = MagicMock(return_value=mock_instance)
|
|
|
|
result = RecommendedAppService.get_recommend_app_detail(app_id, session=sqlite_session)
|
|
|
|
assert result == expected
|
|
mock_instance.get_recommend_app_detail.assert_called_once_with(app_id, session=sqlite_session)
|
|
|
|
@patch("services.recommended_app_service.FeatureService", autospec=True)
|
|
@patch("services.recommended_app_service.RecommendAppRetrievalFactory", autospec=True)
|
|
@patch("services.recommended_app_service.dify_config")
|
|
def test_different_modes(
|
|
self,
|
|
mock_config: MagicMock,
|
|
mock_factory_class: MagicMock,
|
|
mock_feature_service: MagicMock,
|
|
sqlite_session: Session,
|
|
) -> None:
|
|
mock_feature_service.get_system_features.return_value = SystemFeatureModel(enable_trial_app=False)
|
|
for mode in ["remote", "builtin", "db"]:
|
|
mock_config.HOSTED_FETCH_APP_TEMPLATES_MODE = mode
|
|
detail = _app_detail(app_id="test-app", name=f"App from {mode}")
|
|
mock_instance = MagicMock()
|
|
mock_instance.get_recommend_app_detail.return_value = detail
|
|
mock_factory_class.get_recommend_app_factory.return_value = MagicMock(return_value=mock_instance)
|
|
|
|
result = RecommendedAppService.get_recommend_app_detail("test-app", session=sqlite_session)
|
|
|
|
assert result is not None
|
|
mock_instance.get_recommend_app_detail.assert_called_with("test-app", session=sqlite_session)
|
|
mock_factory_class.get_recommend_app_factory.assert_called_with(mode)
|
|
|
|
|
|
# ── Pure logic tests: get_learn_dify_apps ──────────────────────────────
|
|
|
|
|
|
class TestRecommendedAppServiceGetLearnDifyApps:
|
|
@patch("services.recommended_app_service.FeatureService", autospec=True)
|
|
@patch("services.recommended_app_service.RecommendAppRetrievalFactory", autospec=True)
|
|
@patch("services.recommended_app_service.dify_config")
|
|
def test_uses_configured_retrieval_source(
|
|
self,
|
|
mock_config: MagicMock,
|
|
mock_factory_class: MagicMock,
|
|
mock_feature_service: MagicMock,
|
|
sqlite_session: Session,
|
|
) -> None:
|
|
mock_config.HOSTED_FETCH_APP_TEMPLATES_MODE = "remote"
|
|
mock_feature_service.get_system_features.return_value = SystemFeatureModel(enable_trial_app=False)
|
|
expected_app = RecommendedAppPayload(app_id="app-1", category="Workflow")
|
|
mock_instance = MagicMock()
|
|
mock_instance.get_learn_dify_apps.return_value = {
|
|
"recommended_apps": [expected_app],
|
|
"categories": ["Workflow"],
|
|
}
|
|
mock_factory_class.get_recommend_app_factory.return_value = MagicMock(return_value=mock_instance)
|
|
|
|
result = RecommendedAppService.get_learn_dify_apps("en-US", session=sqlite_session)
|
|
|
|
assert result == {"recommended_apps": [expected_app]}
|
|
mock_factory_class.get_recommend_app_factory.assert_called_once_with("remote")
|
|
mock_instance.get_learn_dify_apps.assert_called_once_with("en-US", session=sqlite_session)
|
|
|
|
@patch("services.recommended_app_service.dify_config")
|
|
def test_sets_can_trial_when_trial_feature_enabled(
|
|
self, mock_config: MagicMock, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session
|
|
) -> None:
|
|
mock_config.HOSTED_FETCH_APP_TEMPLATES_MODE = "db"
|
|
app = RecommendedAppPayload(app_id="app-1", category="Workflow")
|
|
mock_retrieval_instance = MagicMock()
|
|
mock_retrieval_instance.get_learn_dify_apps.return_value = {
|
|
"recommended_apps": [app],
|
|
"categories": ["Workflow"],
|
|
}
|
|
mock_retrieval_factory = MagicMock(return_value=mock_retrieval_instance)
|
|
monkeypatch.setattr(
|
|
service_module.RecommendAppRetrievalFactory,
|
|
"get_recommend_app_factory",
|
|
MagicMock(return_value=mock_retrieval_factory),
|
|
)
|
|
monkeypatch.setattr(
|
|
service_module.FeatureService,
|
|
"get_system_features",
|
|
MagicMock(return_value=SystemFeatureModel(enable_trial_app=True)),
|
|
)
|
|
can_trial_mock = MagicMock(return_value=True)
|
|
monkeypatch.setattr(RecommendedAppService, "_can_trial_app", can_trial_mock)
|
|
|
|
result = RecommendedAppService.get_learn_dify_apps("en-US", session=sqlite_session)
|
|
|
|
assert result["recommended_apps"][0]["can_trial"] is True
|
|
can_trial_mock.assert_called_once_with(sqlite_session, "app-1")
|
|
|
|
|
|
# ── Integration tests: trial app features (real DB) ────────────────────
|
|
|
|
|
|
class TestRecommendedAppServiceTrialFeatures:
|
|
def test_get_apps_should_not_query_trial_table_when_disabled(
|
|
self, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session
|
|
) -> None:
|
|
expected = AppsResponse(recommended_apps=[RecommendedAppPayload(app_id="app-1")], categories=["all"])
|
|
retrieval_instance, builtin_instance = _mock_factory_for_apps(monkeypatch, mode="remote", result=expected)
|
|
monkeypatch.setattr(
|
|
service_module.FeatureService,
|
|
"get_system_features",
|
|
MagicMock(return_value=SystemFeatureModel(enable_trial_app=False)),
|
|
)
|
|
|
|
result = RecommendedAppService.get_recommended_apps_and_categories("en-US", session=sqlite_session)
|
|
|
|
assert result == expected
|
|
retrieval_instance.get_recommended_apps_and_categories.assert_called_once_with("en-US", session=sqlite_session)
|
|
builtin_instance.fetch_recommended_apps_from_builtin.assert_not_called()
|
|
|
|
def test_get_apps_should_enrich_can_trial_when_enabled(
|
|
self, sqlite_session: Session, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
app_id_1 = str(uuid.uuid4())
|
|
app_id_2 = str(uuid.uuid4())
|
|
tenant_id = str(uuid.uuid4())
|
|
|
|
# app_id_1 has a TrialApp record; app_id_2 does not
|
|
sqlite_session.add(TrialApp(app_id=app_id_1, tenant_id=tenant_id))
|
|
sqlite_session.commit()
|
|
|
|
remote_result = AppsResponse(recommended_apps=[], categories=[])
|
|
fallback_result = AppsResponse(
|
|
recommended_apps=[RecommendedAppPayload(app_id=app_id_1), RecommendedAppPayload(app_id=app_id_2)],
|
|
categories=["all"],
|
|
)
|
|
_, builtin_instance = _mock_factory_for_apps(
|
|
monkeypatch, mode="remote", result=remote_result, fallback_result=fallback_result
|
|
)
|
|
monkeypatch.setattr(
|
|
service_module.FeatureService,
|
|
"get_system_features",
|
|
MagicMock(return_value=SystemFeatureModel(enable_trial_app=True)),
|
|
)
|
|
|
|
result = RecommendedAppService.get_recommended_apps_and_categories("ja-JP", session=sqlite_session)
|
|
|
|
builtin_instance.fetch_recommended_apps_from_builtin.assert_called_once_with("en-US")
|
|
assert result["recommended_apps"][0]["can_trial"] is True
|
|
assert result["recommended_apps"][1]["can_trial"] is False
|
|
|
|
@pytest.mark.parametrize("has_trial_app", [True, False])
|
|
def test_get_detail_should_set_can_trial_when_enabled(
|
|
self,
|
|
sqlite_session: Session,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
has_trial_app: bool,
|
|
) -> None:
|
|
app_id = str(uuid.uuid4())
|
|
tenant_id = str(uuid.uuid4())
|
|
|
|
if has_trial_app:
|
|
sqlite_session.add(TrialApp(app_id=app_id, tenant_id=tenant_id))
|
|
sqlite_session.commit()
|
|
|
|
detail = RecommendedAppPayload(id=app_id, name="Test App")
|
|
retrieval_instance = MagicMock()
|
|
retrieval_instance.get_recommend_app_detail.return_value = detail
|
|
retrieval_factory = MagicMock(return_value=retrieval_instance)
|
|
monkeypatch.setattr(service_module.dify_config, "HOSTED_FETCH_APP_TEMPLATES_MODE", "remote", raising=False)
|
|
monkeypatch.setattr(
|
|
service_module.RecommendAppRetrievalFactory,
|
|
"get_recommend_app_factory",
|
|
MagicMock(return_value=retrieval_factory),
|
|
)
|
|
monkeypatch.setattr(
|
|
service_module.FeatureService,
|
|
"get_system_features",
|
|
MagicMock(return_value=SystemFeatureModel(enable_trial_app=True)),
|
|
)
|
|
|
|
result = RecommendedAppService.get_recommend_app_detail(app_id, session=sqlite_session)
|
|
assert result is not None
|
|
detail_result = cast(RecommendedAppPayload, result)
|
|
|
|
assert detail_result["id"] == app_id
|
|
assert detail_result["can_trial"] is has_trial_app
|
|
|
|
@patch("services.recommended_app_service.FeatureService", autospec=True)
|
|
@patch("services.recommended_app_service.RecommendAppRetrievalFactory", autospec=True)
|
|
@patch("services.recommended_app_service.dify_config")
|
|
def test_get_detail_returns_none_before_reading_trial_flag(
|
|
self,
|
|
mock_config: MagicMock,
|
|
mock_factory_class: MagicMock,
|
|
mock_feature_service: MagicMock,
|
|
sqlite_session: Session,
|
|
) -> None:
|
|
mock_config.HOSTED_FETCH_APP_TEMPLATES_MODE = "remote"
|
|
mock_instance = MagicMock()
|
|
mock_instance.get_recommend_app_detail.return_value = None
|
|
mock_factory_class.get_recommend_app_factory.return_value = MagicMock(return_value=mock_instance)
|
|
|
|
result = RecommendedAppService.get_recommend_app_detail("nonexistent", session=sqlite_session)
|
|
|
|
assert result is None
|
|
mock_instance.get_recommend_app_detail.assert_called_once_with("nonexistent", session=sqlite_session)
|
|
mock_feature_service.get_system_features.assert_not_called()
|
|
|
|
def test_add_trial_app_record_increments_count_for_existing(self, sqlite_session: Session) -> None:
|
|
app_id = str(uuid.uuid4())
|
|
account_id = str(uuid.uuid4())
|
|
|
|
sqlite_session.add(AccountTrialAppRecord(app_id=app_id, account_id=account_id, count=3))
|
|
sqlite_session.commit()
|
|
|
|
RecommendedAppService.add_trial_app_record(app_id, account_id, session=sqlite_session)
|
|
|
|
sqlite_session.expire_all()
|
|
record = sqlite_session.scalar(
|
|
select(AccountTrialAppRecord)
|
|
.where(AccountTrialAppRecord.app_id == app_id, AccountTrialAppRecord.account_id == account_id)
|
|
.limit(1)
|
|
)
|
|
assert record is not None
|
|
assert record.count == 4
|
|
|
|
def test_add_trial_app_record_creates_new_record(self, sqlite_session: Session) -> None:
|
|
app_id = str(uuid.uuid4())
|
|
account_id = str(uuid.uuid4())
|
|
|
|
RecommendedAppService.add_trial_app_record(app_id, account_id, session=sqlite_session)
|
|
|
|
sqlite_session.expire_all()
|
|
record = sqlite_session.scalar(
|
|
select(AccountTrialAppRecord)
|
|
.where(AccountTrialAppRecord.app_id == app_id, AccountTrialAppRecord.account_id == account_id)
|
|
.limit(1)
|
|
)
|
|
assert record is not None
|
|
assert record.app_id == app_id
|
|
assert record.account_id == account_id
|
|
assert record.count == 1
|