From 09358f07f251632fdde489b98bdbddc0f1e83cf0 Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 15 Sep 2026 22:09:17 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=82=D0=B0=D0=BB=D0=BE=D0=B3=20=D0=BF=D0=BE=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=98=D0=98=20=D0=B8=20=D0=BF=D0=BE=D0=BA?= =?UTF-8?q?=D1=80=D1=8B=D1=82=D1=8C=20=D0=B1=D0=B8=D1=82=D1=8B=D0=B9=20?= =?UTF-8?q?=D1=88=D0=B8=D1=84=D1=80=D0=BE=D1=82=D0=B5=D0=BA=D1=81=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Application/Services/AiGlobalConfigService.cs | 11 ++++------- .../Api/AiGlobalConfigServiceTests.cs | 13 +++++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/core/Deal.Modules.Settings/Application/Services/AiGlobalConfigService.cs b/src/core/Deal.Modules.Settings/Application/Services/AiGlobalConfigService.cs index 28c029e..262ed97 100644 --- a/src/core/Deal.Modules.Settings/Application/Services/AiGlobalConfigService.cs +++ b/src/core/Deal.Modules.Settings/Application/Services/AiGlobalConfigService.cs @@ -27,11 +27,8 @@ public sealed class AiGlobalConfigService(IGlobalSettingsStore store, ISecretCip // задаёт оператор (остальным облачным адрес фиксирован каталогом, SSRF-гейт). private const string CustomProviderId = "custom"; - // Ключи JSON значения aiConfig (camelCase, как пишет SaveAsync). + // Ключ JSON значения aiConfig с id провайдера (остальные поля — SettingsFieldKeys). private const string ProviderIdProperty = "providerId"; - private const string ApiKeyProperty = "apiKey"; - private const string BaseUrlProperty = "baseUrl"; - private const string ModelProperty = "model"; // Геометрия маски секрета: короткий (≤8) → «x…»; иначе «1234…5678». private const int MaskShortMaxLength = 8; @@ -81,19 +78,19 @@ public sealed class AiGlobalConfigService(IGlobalSettingsStore store, ISecretCip return EmptySnapshot(); } - string baseUrl = ReadString(root, BaseUrlProperty); + string baseUrl = ReadString(root, SettingsFieldKeys.BaseUrl); if (baseUrl.Length == 0 || !IsBaseUrlMutable(meta)) { baseUrl = meta.Base; } - string model = ReadString(root, ModelProperty); + string model = ReadString(root, SettingsFieldKeys.Model); if (model.Length == 0) { model = meta.Models.FirstOrDefault() ?? string.Empty; } - string apiKey = cipher.Decrypt(ReadString(root, ApiKeyProperty)); + string apiKey = cipher.Decrypt(ReadString(root, SettingsFieldKeys.ApiKey)); return new AiGlobalConfigSnapshot(providerId, baseUrl, model, apiKey); } catch (JsonException) diff --git a/src/core/tests/Deal.Tests.Unit/Api/AiGlobalConfigServiceTests.cs b/src/core/tests/Deal.Tests.Unit/Api/AiGlobalConfigServiceTests.cs index a7c3a76..c1e3608 100644 --- a/src/core/tests/Deal.Tests.Unit/Api/AiGlobalConfigServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/Api/AiGlobalConfigServiceTests.cs @@ -116,6 +116,19 @@ public sealed class AiGlobalConfigServiceTests Assert.False(snapshot.Configured); } + [Fact] + public async Task GetSnapshotAsync_BrokenCiphertext_LeavesKeyEmptyWithoutThrowing() + { + _store.Preload( + GlobalSettingsKeys.AiConfig, + "{\"providerId\":\"deepseek\",\"baseUrl\":\"\",\"model\":\"\",\"apiKey\":\"enc:не-base64!\"}"); + + AiGlobalConfigSnapshot snapshot = await _service.GetSnapshotAsync(CancellationToken.None); + + Assert.Equal(string.Empty, snapshot.ApiKey); + Assert.False(snapshot.Configured); + } + [Theory] [InlineData("sk-operator-key-123", true)] [InlineData("sk-1…-123", false)] // маска из снимка