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)] // маска из снимка