From 936a09c70494ecbf83a1a52a10a726136241b80a Mon Sep 17 00:00:00 2001 From: EvanYao <2869018789@qq.com> Date: Fri, 8 May 2026 11:18:21 +0800 Subject: [PATCH 1/3] fix: replace SimpleNamespace with MagicMock(spec=App) in _app_stub (#34636) (#35897) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../services/test_app_dsl_service.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/tests/test_containers_integration_tests/services/test_app_dsl_service.py b/api/tests/test_containers_integration_tests/services/test_app_dsl_service.py index 6b844615b5..7c5d2390ba 100644 --- a/api/tests/test_containers_integration_tests/services/test_app_dsl_service.py +++ b/api/tests/test_containers_integration_tests/services/test_app_dsl_service.py @@ -3,7 +3,7 @@ from __future__ import annotations import base64 import json from types import SimpleNamespace -from typing import Any, cast +from typing import Any from unittest.mock import MagicMock, patch from uuid import uuid4 @@ -71,6 +71,7 @@ def _pending_yaml_content(version: str = "99.0.0") -> bytes: def _app_stub(**overrides: Any) -> App: + """Create a stub App object for testing without hitting the database.""" defaults = { "id": str(uuid4()), "tenant_id": _DEFAULT_TENANT_ID, @@ -83,7 +84,10 @@ def _app_stub(**overrides: Any) -> App: "use_icon_as_answer_icon": False, "app_model_config": None, } - return cast(App, SimpleNamespace(**(defaults | overrides))) + app = MagicMock(spec=App) + for key, value in (defaults | overrides).items(): + object.__setattr__(app, key, value) + return app class TestAppDslService: From 1b0d4637b3e62a1f8a110fc8e5405a254c3d37bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baki=20Burak=20=C3=96=C4=9F=C3=BCn?= <63836730+bakiburakogun@users.noreply.github.com> Date: Fri, 8 May 2026 06:24:02 +0300 Subject: [PATCH 2/3] fix(i18n): update Turkish translations for new strings (#35905) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Baki Burak Öğün Co-authored-by: Crazywoola <100913391+crazywoola@users.noreply.github.com> --- web/i18n/tr-TR/app-debug.json | 10 +++---- web/i18n/tr-TR/app.json | 4 +-- web/i18n/tr-TR/education.json | 8 +++--- web/i18n/tr-TR/share.json | 4 +-- web/i18n/tr-TR/workflow.json | 50 +++++++++++++++++------------------ 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/web/i18n/tr-TR/app-debug.json b/web/i18n/tr-TR/app-debug.json index df14e4c175..4c7b7c85d1 100644 --- a/web/i18n/tr-TR/app-debug.json +++ b/web/i18n/tr-TR/app-debug.json @@ -261,14 +261,14 @@ "notSetAPIKey.trailFinished": "Deneme süresi sona erdi", "notSetVar": "Değişkenler, kullanıcıların form doldururken prompt kelimelerini veya açılış ifadelerini getirmesine izin verir. Prompt kelimelerine \"{{input}}\" yazmayı deneyebilirsiniz.", "openingStatement.add": "Ekle", - "openingStatement.editorTitle": "Acilis Mesaji", + "openingStatement.editorTitle": "Açılış Mesajı", "openingStatement.noDataPlaceHolder": "Kullanıcı ile konuşmayı başlatmak, AI'ın konuşma uygulamalarında onlarla daha yakın bir bağlantı kurmasına yardımcı olabilir.", "openingStatement.notIncludeKey": "Başlangıç promptu değişkeni içermiyor: {{key}}. Lütfen bunu başlangıç promptuna ekleyin.", "openingStatement.openingQuestion": "Açılış Soruları", - "openingStatement.openingQuestionDescription": "Acilis mesajindan sonra gosterilen istege bagli yonlendirmeler; kullanicilarin konusmayi surdurmesine yardimci olur.", - "openingStatement.openingQuestionPlaceholder": "Bir acilis sorusu girin.", - "openingStatement.placeholderLine1": "Buradan baslayin. Yapay zekanin gondermesi gereken ilk mesaji yazin.", - "openingStatement.placeholderLine2": "Değişkenler kullanabilirsiniz, {{variable}} yazmayı deneyin.", + "openingStatement.openingQuestionDescription": "Açılış mesajından sonra gösterilen isteğe bağlı yönlendirmeler; kullanıcıların konuşmayı sürdürmesine yardımcı olur.", + "openingStatement.openingQuestionPlaceholder": "Bir açılış sorusu girin.", + "openingStatement.placeholderLine1": "Buradan başlayın. Yapay zekânın göndermesi gereken ilk mesajı yazın.", + "openingStatement.placeholderLine2": "Değişkenleri kullanabilirsiniz; {{variable}} yazmayı deneyin.", "openingStatement.title": "Konuşma Başlatıcı", "openingStatement.tooShort": "Konuşma için açılış ifadeleri oluşturmak için en az 20 kelimelik başlangıç promptu gereklidir.", "openingStatement.varTip": "Değişkenler kullanabilirsiniz, örneğin {{variable}} yazmayı deneyin", diff --git a/web/i18n/tr-TR/app.json b/web/i18n/tr-TR/app.json index aa10f954e9..e2f3f9aad4 100644 --- a/web/i18n/tr-TR/app.json +++ b/web/i18n/tr-TR/app.json @@ -136,11 +136,11 @@ "marketplace.template.fetchFailed": "Şablon alınamadı", "marketplace.template.importConfirm": "İçe Aktar", "marketplace.template.importFailed": "Şablon içe aktarılamadı", - "marketplace.template.modalTitle": "Marketplace'den İçe Aktar", + "marketplace.template.modalTitle": "Pazar Yeri'nden İçe Aktar", "marketplace.template.overview": "Genel Bakış", "marketplace.template.publishedBy": "Yayıncı", "marketplace.template.usageCount": "Kullanım", - "marketplace.template.viewOnMarketplace": "Marketplace'de Görüntüle", + "marketplace.template.viewOnMarketplace": "Pazar Yeri'nde Görüntüle", "maxActiveRequests": "Maksimum eş zamanlı istekler", "maxActiveRequestsPlaceholder": "Sınırsız için 0 girin", "maxActiveRequestsTip": "Her uygulama için maksimum eşzamanlı aktif istek sayısı (sınırsız için 0)", diff --git a/web/i18n/tr-TR/education.json b/web/i18n/tr-TR/education.json index 736407f362..61e03379b4 100644 --- a/web/i18n/tr-TR/education.json +++ b/web/i18n/tr-TR/education.json @@ -1,24 +1,24 @@ { "applied.activeSubscription.description": "Aktif bir aboneliğiniz var. Aboneliğinizin süresi dolduktan sonra eğitim indirimini kullanabilirsiniz. Aboneliğinizi Stripe'da onaylayın.", "applied.description": "Tebrikler! Eğitim indirimi için başarıyla başvurdunuz.", - "applied.noPaymentPermission.description": "Bu workspace'te ödeme izniniz yok. Eğitim indirimini kullanmak için lütfen faturalamayı yönetebileceğiniz bir workspace'e geçin.", + "applied.noPaymentPermission.description": "Bu çalışma alanında ödeme izniniz yok. Eğitim indirimini kullanmak için lütfen faturalamayı yönetebileceğiniz bir çalışma alanına geçin.", "applied.noPaymentPermission.returnHome": "Dify'e geri dön", "applied.step1.description": "Eğitim indirimi için başarıyla başvurdunuz.", "applied.step1.title": "Adım 1", - "applied.step2.description": "Eğitim indirimiyle kullanmak istediğiniz workspace'i seçin.", + "applied.step2.description": "Eğitim indirimiyle kullanmak istediğiniz çalışma alanını seçin.", "applied.step2.title": "Adım 2", "applied.tabs.activeSubscription": "Abonelikte", "applied.tabs.eligible": "Satın alabilir", "applied.tabs.noPaymentPermission": "Ödeme izni yok", "applied.title": "Eğitim indirimi uygulandı", "applied.workspace.plan": "Ücretli plan", - "applied.workspace.title": "Mevcut Workspace", + "applied.workspace.title": "Mevcut Çalışma Alanı", "currentSigned": "ŞU ANDA GİRİŞ YAPILDIĞI KİŞİ", "educationPricingConfirm.billingPeriod.monthly": "aylık", "educationPricingConfirm.billingPeriod.yearly": "yıllık", "educationPricingConfirm.cancel": "İptal", "educationPricingConfirm.continue": "İndirim olmadan devam et", - "educationPricingConfirm.description": "{{planName}} {{billingPeriod}} planınız eğitim indirimini desteklemiyor. Yalnızca Professional yıllık plan uygun.", + "educationPricingConfirm.description": "{{planName}} {{billingPeriod}} planınız eğitim indirimini desteklemiyor. Yalnızca yıllık Professional planı uygundur.", "educationPricingConfirm.title": "Eğitim indirimi mevcut değil", "emailLabel": "Şu anki e-posta adresin", "form.schoolName.placeholder": "Okulunuzun resmi, kısaltılmamış adını girin", diff --git a/web/i18n/tr-TR/share.json b/web/i18n/tr-TR/share.json index 8dc64335ea..7ec9d02ea8 100644 --- a/web/i18n/tr-TR/share.json +++ b/web/i18n/tr-TR/share.json @@ -16,8 +16,8 @@ "chat.privacyPolicyMiddle": "gizlilik politikası", "chat.privacyPolicyRight": "uygulama geliştiricisi tarafından sağlanmıştır.", "chat.privatePromptConfigTitle": "Konuşma ayarları", - "chat.prompt": "Prompt", - "chat.publicPromptConfigTitle": "Başlangıç Promptu", + "chat.prompt": "İstem", + "chat.publicPromptConfigTitle": "Başlangıç İstemi", "chat.resetChat": "Konuşmayı sıfırla", "chat.startChat": "Sohbete Başla", "chat.temporarySystemIssue": "Üzgünüz, geçici sistem sorunu.", diff --git a/web/i18n/tr-TR/workflow.json b/web/i18n/tr-TR/workflow.json index 7cd69d7df1..af30ebf4ac 100644 --- a/web/i18n/tr-TR/workflow.json +++ b/web/i18n/tr-TR/workflow.json @@ -111,33 +111,33 @@ "chatVariable.updatedAt": "Güncellenme zamanı: ", "collaboration.historyAction.generic": "Bir işbirlikçi geri alma/yeniden yapma gerçekleştirdi", "comments.actions.addComment": "Yorum ekle", - "comments.actions.deleteReply": "Delete reply", + "comments.actions.deleteReply": "Yanıtı sil", "comments.actions.editComment": "Yorumu düzenle", - "comments.actions.editReply": "Edit reply", - "comments.aria.closeComment": "Close comment", + "comments.actions.editReply": "Yanıtı düzenle", + "comments.aria.closeComment": "Yorumu kapat", "comments.aria.commentActions": "Yorum işlemleri", - "comments.aria.deleteComment": "Delete thread", + "comments.aria.deleteComment": "Konuyu sil", "comments.aria.filterComments": "Yorumları filtrele", - "comments.aria.nextComment": "Next comment", - "comments.aria.previousComment": "Previous comment", - "comments.aria.replyActions": "Reply actions", - "comments.aria.resolveComment": "Resolve", - "comments.confirm.deleteReplyDesc": "This reply will be removed permanently.", - "comments.confirm.deleteReplyTitle": "Delete this reply?", - "comments.confirm.deleteThreadDesc": "This action will permanently delete the thread and all its replies. This cannot be undone.", - "comments.confirm.deleteThreadTitle": "Delete this thread?", - "comments.fallback.user": "User", + "comments.aria.nextComment": "Sonraki yorum", + "comments.aria.previousComment": "Önceki yorum", + "comments.aria.replyActions": "Yanıt işlemleri", + "comments.aria.resolveComment": "Çöz", + "comments.confirm.deleteReplyDesc": "Bu yanıt kalıcı olarak kaldırılacak.", + "comments.confirm.deleteReplyTitle": "Bu yanıt silinsin mi?", + "comments.confirm.deleteThreadDesc": "Bu işlem konuyu ve tüm yanıtlarını kalıcı olarak siler. Bu işlem geri alınamaz.", + "comments.confirm.deleteThreadTitle": "Bu konu silinsin mi?", + "comments.fallback.user": "Kullanıcı", "comments.filter.all": "Tümü", - "comments.filter.onlyYourThreads": "Yalnızca senin başlıkların", + "comments.filter.onlyYourThreads": "Yalnızca kendi konuların", "comments.filter.showResolved": "Çözülenleri göster", - "comments.loading": "Loading…", - "comments.noComments": "No comments yet", - "comments.panelTitle": "Comment", - "comments.placeholder.add": "Add a comment", + "comments.loading": "Yükleniyor…", + "comments.noComments": "Henüz yorum yok", + "comments.panelTitle": "Yorum", + "comments.placeholder.add": "Yorum ekle", "comments.placeholder.editComment": "Yorumu düzenle", - "comments.placeholder.editReply": "Edit reply", - "comments.placeholder.reply": "Reply", - "comments.reply": "Reply", + "comments.placeholder.editReply": "Yanıtı düzenle", + "comments.placeholder.reply": "Yanıtla", + "comments.reply": "Yanıtla", "common.ImageUploadLegacyTip": "Artık başlangıç formunda dosya türü değişkenleri oluşturabilirsiniz. Gelecekte resim yükleme özelliğini artık desteklemeyeceğiz.", "common.accessAPIReference": "API Referansına Eriş", "common.addBlock": "Düğüm Ekle", @@ -229,8 +229,8 @@ "common.previewPlaceholder": "Sohbet Robotunu hata ayıklamak için aşağıdaki kutuya içerik girin", "common.processData": "Veriyi İşle", "common.publish": "Yayınla", - "common.publishToMarketplace": "Marketplace'de Yayınla", - "common.publishToMarketplaceFailed": "Marketplace'de Yayınlama Başarısız", + "common.publishToMarketplace": "Pazar Yeri'nde Yayınla", + "common.publishToMarketplaceFailed": "Pazar Yeri'nde yayınlanamadı", "common.publishUpdate": "Güncellemeyi Yayınla", "common.published": "Yayınlandı", "common.publishedAt": "Yayınlandı", @@ -812,7 +812,7 @@ "nodes.llm.outputVars.output": "İçerik Üret", "nodes.llm.outputVars.reasoning_content": "Akıl yürütme içeriği", "nodes.llm.outputVars.usage": "Model Kullanım Bilgileri", - "nodes.llm.prompt": "prompt", + "nodes.llm.prompt": "istem", "nodes.llm.reasoningFormat.separated": "Ayrı düşünce etiketleri", "nodes.llm.reasoningFormat.tagged": "Etiketleri düşünmeye devam et", "nodes.llm.reasoningFormat.title": "Akıl yürütme etiket ayrımını etkinleştir", @@ -891,7 +891,7 @@ "nodes.parameterExtractor.outputVars.usage": "Model Kullanım Bilgileri", "nodes.parameterExtractor.reasoningMode": "Akıl Yürütme Modu", "nodes.parameterExtractor.reasoningModeFunctionToolCalling": "Fonksiyon/Araç Çağrısı", - "nodes.parameterExtractor.reasoningModePrompt": "Prompt", + "nodes.parameterExtractor.reasoningModePrompt": "İstem", "nodes.parameterExtractor.reasoningModeTip": "Modelin fonksiyon çağırma veya istemler için talimatlara yanıt verme yeteneğine bağlı olarak uygun akıl yürütme modunu seçebilirsiniz.", "nodes.questionClassifiers.addClass": "Sınıf Ekle", "nodes.questionClassifiers.advancedSetting": "Gelişmiş Ayarlar", From 29f34848cd90f09af19d64ad155b149987992cb1 Mon Sep 17 00:00:00 2001 From: Xiyuan Chen <52963600+GareArc@users.noreply.github.com> Date: Thu, 7 May 2026 22:08:23 -0700 Subject: [PATCH 3/3] fix(tools): scope builtin tool default-credential clear to tenant (#35887) --- .../console/workspace/tool_providers.py | 4 ++-- .../tools/builtin_tools_manage_service.py | 5 ++--- .../test_builtin_tools_manage_service.py | 22 +++++++++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/api/controllers/console/workspace/tool_providers.py b/api/controllers/console/workspace/tool_providers.py index 34c9534de8..e653c9064c 100644 --- a/api/controllers/console/workspace/tool_providers.py +++ b/api/controllers/console/workspace/tool_providers.py @@ -876,10 +876,10 @@ class ToolBuiltinProviderSetDefaultApi(Resource): @login_required @account_initialization_required def post(self, provider): - current_user, current_tenant_id = current_account_with_tenant() + _, current_tenant_id = current_account_with_tenant() payload = BuiltinProviderDefaultCredentialPayload.model_validate(console_ns.payload or {}) return BuiltinToolManageService.set_default_provider( - tenant_id=current_tenant_id, user_id=current_user.id, provider=provider, id=payload.id + tenant_id=current_tenant_id, provider=provider, id=payload.id ) diff --git a/api/services/tools/builtin_tools_manage_service.py b/api/services/tools/builtin_tools_manage_service.py index b8242ab3a5..20de1f4058 100644 --- a/api/services/tools/builtin_tools_manage_service.py +++ b/api/services/tools/builtin_tools_manage_service.py @@ -408,7 +408,7 @@ class BuiltinToolManageService: return {"result": "success"} @staticmethod - def set_default_provider(tenant_id: str, user_id: str, provider: str, id: str): + def set_default_provider(tenant_id: str, provider: str, id: str): """ set default provider """ @@ -422,12 +422,11 @@ class BuiltinToolManageService: if target_provider is None: raise ValueError("provider not found") - # clear default provider + # clear default provider (tenant-scoped: only one default per provider per workspace) session.execute( update(BuiltinToolProvider) .where( BuiltinToolProvider.tenant_id == tenant_id, - BuiltinToolProvider.user_id == user_id, BuiltinToolProvider.provider == provider, BuiltinToolProvider.is_default.is_(True), ) diff --git a/api/tests/unit_tests/services/tools/test_builtin_tools_manage_service.py b/api/tests/unit_tests/services/tools/test_builtin_tools_manage_service.py index ce0d94398d..c210db580e 100644 --- a/api/tests/unit_tests/services/tools/test_builtin_tools_manage_service.py +++ b/api/tests/unit_tests/services/tools/test_builtin_tools_manage_service.py @@ -180,7 +180,7 @@ class TestSetDefaultProvider: session.scalar.return_value = None with pytest.raises(ValueError, match="provider not found"): - BuiltinToolManageService.set_default_provider("t", "u", "p", "id") + BuiltinToolManageService.set_default_provider("t", "p", "id") @patch(f"{MODULE}.sessionmaker") @patch(f"{MODULE}.db") @@ -189,11 +189,29 @@ class TestSetDefaultProvider: target = MagicMock() session.scalar.return_value = target - result = BuiltinToolManageService.set_default_provider("t", "u", "p", "id") + result = BuiltinToolManageService.set_default_provider("t", "p", "id") assert result == {"result": "success"} assert target.is_default is True + @patch(f"{MODULE}.sessionmaker") + @patch(f"{MODULE}.db") + def test_clear_default_is_tenant_scoped_not_user_scoped(self, mock_db, mock_sm_cls): + # Regression: clearing prior defaults must NOT filter by user_id, otherwise + # two workspace members can each leave their own credential as default at + # the same time (the default flag is tenant-scoped, not per-user). + session = _mock_sessionmaker(mock_sm_cls) + session.scalar.return_value = MagicMock() + + BuiltinToolManageService.set_default_provider("tenant-1", "google", "cred-id") + + session.execute.assert_called_once() + update_stmt = session.execute.call_args.args[0] + compiled = str(update_stmt.compile(compile_kwargs={"literal_binds": True})) + assert "user_id" not in compiled + assert "tenant_id" in compiled + assert "provider" in compiled + class TestUpdateBuiltinToolProvider: @patch(f"{MODULE}.sessionmaker")