Хелпер Support/TestCiphers.New: детерминированная семантика enc:base64 через Returns-лямбды. Потребители (8 файлов) перетипизированы на ISecretCipher, фейк удалён, тесты 1340 зелёные.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using Deal.Api.Telegram;
|
using Deal.Api.Telegram;
|
||||||
|
using Deal.Modules.Settings.Application.Abstractions;
|
||||||
using Deal.Modules.Settings.Application.Models;
|
using Deal.Modules.Settings.Application.Models;
|
||||||
using Deal.Tests.Unit.Modules.Settings;
|
using Deal.Tests.Unit.Modules.Settings;
|
||||||
using Deal.Tests.Unit.Support;
|
using Deal.Tests.Unit.Support;
|
||||||
@@ -11,7 +12,7 @@ namespace Deal.Tests.Unit.Api;
|
|||||||
public sealed class TelegramKeysServiceTests
|
public sealed class TelegramKeysServiceTests
|
||||||
{
|
{
|
||||||
private readonly TestGlobalSettingsStore _store = new();
|
private readonly TestGlobalSettingsStore _store = new();
|
||||||
private readonly FakeSecretCipher _cipher = new();
|
private readonly ISecretCipher _cipher = TestCiphers.New();
|
||||||
private readonly TelegramKeysService _service;
|
private readonly TelegramKeysService _service;
|
||||||
|
|
||||||
public TelegramKeysServiceTests()
|
public TelegramKeysServiceTests()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Deal.Api.Telegram;
|
using Deal.Api.Telegram;
|
||||||
using Deal.Contracts.Integrations.Models;
|
using Deal.Contracts.Integrations.Models;
|
||||||
|
using Deal.Modules.Settings.Application.Abstractions;
|
||||||
using Deal.Modules.Settings.Application.Models;
|
using Deal.Modules.Settings.Application.Models;
|
||||||
using Deal.Modules.Telegram.Application;
|
using Deal.Modules.Telegram.Application;
|
||||||
using Deal.Modules.Telegram.Application.Models;
|
using Deal.Modules.Telegram.Application.Models;
|
||||||
@@ -104,13 +105,13 @@ public sealed class TgStatusServiceTests
|
|||||||
|
|
||||||
// ─── Хелперы ────────────────────────────────────────────────────────────
|
// ─── Хелперы ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Собирает сервис на фейках (FakeTelegramGateway/Store/SettingsStore/GlobalSettingsStore + FakeSecretCipher).
|
// Собирает сервис на фейках (FakeTelegramGateway/Store/SettingsStore/GlobalSettingsStore + ISecretCipher).
|
||||||
private static (TgStatusService Service, TestTelegramStore Store, FakeSettingsStore Settings, TestTelegramGateway Gateway, FakeSecretCipher Cipher, TestGlobalSettingsStore GlobalSettings) Create()
|
private static (TgStatusService Service, TestTelegramStore Store, FakeSettingsStore Settings, TestTelegramGateway Gateway, ISecretCipher Cipher, TestGlobalSettingsStore GlobalSettings) Create()
|
||||||
{
|
{
|
||||||
var store = new TestTelegramStore();
|
var store = new TestTelegramStore();
|
||||||
var settings = new FakeSettingsStore();
|
var settings = new FakeSettingsStore();
|
||||||
var gateway = new TestTelegramGateway();
|
var gateway = new TestTelegramGateway();
|
||||||
var cipher = new FakeSecretCipher();
|
var cipher = TestCiphers.New();
|
||||||
var globalSettings = new TestGlobalSettingsStore();
|
var globalSettings = new TestGlobalSettingsStore();
|
||||||
var keys = new TelegramKeysService(globalSettings.Store, cipher);
|
var keys = new TelegramKeysService(globalSettings.Store, cipher);
|
||||||
var dialogs = new DialogsService(store.Store, settings, gateway.Gateway, NullLogger<DialogsService>.Instance);
|
var dialogs = new DialogsService(store.Store, settings, gateway.Gateway, NullLogger<DialogsService>.Instance);
|
||||||
@@ -127,7 +128,7 @@ public sealed class TgStatusServiceTests
|
|||||||
string apiId,
|
string apiId,
|
||||||
string apiHash)
|
string apiHash)
|
||||||
{
|
{
|
||||||
string encrypted = new FakeSecretCipher().Encrypt(apiHash);
|
string encrypted = TestCiphers.New().Encrypt(apiHash);
|
||||||
settings.Preload(GlobalSettingsKeys.TelegramKeys, $"{{\"apiId\":\"{apiId}\",\"apiHash\":\"{encrypted}\"}}");
|
settings.Preload(GlobalSettingsKeys.TelegramKeys, $"{{\"apiId\":\"{apiId}\",\"apiHash\":\"{encrypted}\"}}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Deal.Infrastructure.Integrations.Exceptions;
|
|||||||
using Deal.Infrastructure.Integrations.Models;
|
using Deal.Infrastructure.Integrations.Models;
|
||||||
using Deal.Infrastructure.Integrations.Options;
|
using Deal.Infrastructure.Integrations.Options;
|
||||||
using Deal.Infrastructure.Integrations.Services;
|
using Deal.Infrastructure.Integrations.Services;
|
||||||
|
using Deal.Modules.Settings.Application.Abstractions;
|
||||||
using Deal.Modules.Settings.Application.Models;
|
using Deal.Modules.Settings.Application.Models;
|
||||||
using Deal.Modules.Tenants.Application.Services;
|
using Deal.Modules.Tenants.Application.Services;
|
||||||
using Deal.SharedKernel.Tenants.Models;
|
using Deal.SharedKernel.Tenants.Models;
|
||||||
@@ -39,7 +40,7 @@ public sealed class GrpcAiToolsTests
|
|||||||
reply.Keywords.Add("telegram каналы");
|
reply.Keywords.Add("telegram каналы");
|
||||||
service.GenerateKeywordsReply = reply;
|
service.GenerateKeywordsReply = reply;
|
||||||
FakeSettingsStore settings = new();
|
FakeSettingsStore settings = new();
|
||||||
FakeSecretCipher cipher = new();
|
ISecretCipher cipher = TestCiphers.New();
|
||||||
FakeTenantLimitStore limits = new();
|
FakeTenantLimitStore limits = new();
|
||||||
|
|
||||||
IAiTools tools = CreateTools(port, settings, cipher, limits);
|
IAiTools tools = CreateTools(port, settings, cipher, limits);
|
||||||
@@ -65,7 +66,7 @@ public sealed class GrpcAiToolsTests
|
|||||||
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
||||||
{
|
{
|
||||||
service.KeywordsUnavailable = true;
|
service.KeywordsUnavailable = true;
|
||||||
IAiTools tools = CreateTools(port, new FakeSettingsStore(), new FakeSecretCipher());
|
IAiTools tools = CreateTools(port, new FakeSettingsStore(), TestCiphers.New());
|
||||||
|
|
||||||
AiGenerateKeywordsResultDto result = await tools.GenerateKeywordsAsync("описание", CancellationToken.None);
|
AiGenerateKeywordsResultDto result = await tools.GenerateKeywordsAsync("описание", CancellationToken.None);
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ public sealed class GrpcAiToolsTests
|
|||||||
Usage = new Usage { Prompt = 200, Completion = 10, Total = 210 },
|
Usage = new Usage { Prompt = 200, Completion = 10, Total = 210 },
|
||||||
};
|
};
|
||||||
FakeTenantLimitStore limits = new();
|
FakeTenantLimitStore limits = new();
|
||||||
IAiTools tools = CreateTools(port, new FakeSettingsStore(), new FakeSecretCipher(), limits);
|
IAiTools tools = CreateTools(port, new FakeSettingsStore(), TestCiphers.New(), limits);
|
||||||
|
|
||||||
AiEvaluateFitResultDto result = await tools.EvaluateFitAsync(
|
AiEvaluateFitResultDto result = await tools.EvaluateFitAsync(
|
||||||
"Ищу дизайнера для лендинга", "Разработка сайтов на Python", new[] { "python", "бэкенд" }, CancellationToken.None);
|
"Ищу дизайнера для лендинга", "Разработка сайтов на Python", new[] { "python", "бэкенд" }, CancellationToken.None);
|
||||||
@@ -112,7 +113,7 @@ public sealed class GrpcAiToolsTests
|
|||||||
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
||||||
{
|
{
|
||||||
service.FitUnavailable = true;
|
service.FitUnavailable = true;
|
||||||
IAiTools tools = CreateTools(port, new FakeSettingsStore(), new FakeSecretCipher());
|
IAiTools tools = CreateTools(port, new FakeSettingsStore(), TestCiphers.New());
|
||||||
|
|
||||||
await Assert.ThrowsAsync<AiUnavailableException>(
|
await Assert.ThrowsAsync<AiUnavailableException>(
|
||||||
() => tools.EvaluateFitAsync("текст", "описание", new[] { "ключ" }, CancellationToken.None));
|
() => tools.EvaluateFitAsync("текст", "описание", new[] { "ключ" }, CancellationToken.None));
|
||||||
@@ -128,7 +129,7 @@ public sealed class GrpcAiToolsTests
|
|||||||
private static GrpcAiTools CreateTools(
|
private static GrpcAiTools CreateTools(
|
||||||
int port,
|
int port,
|
||||||
FakeSettingsStore settings,
|
FakeSettingsStore settings,
|
||||||
FakeSecretCipher cipher,
|
ISecretCipher cipher,
|
||||||
FakeTenantLimitStore? limits = null)
|
FakeTenantLimitStore? limits = null)
|
||||||
{
|
{
|
||||||
ITenantContext tenantContext = new TenantContext();
|
ITenantContext tenantContext = new TenantContext();
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ public sealed class IntegrationsDiTests
|
|||||||
tenantContext.SetTenant(new TenantId(Guid.NewGuid().ToString("N")));
|
tenantContext.SetTenant(new TenantId(Guid.NewGuid().ToString("N")));
|
||||||
services.AddSingleton<ITenantContext>(tenantContext);
|
services.AddSingleton<ITenantContext>(tenantContext);
|
||||||
services.AddScoped<ISettingsStore>(_ => new FakeSettingsStore());
|
services.AddScoped<ISettingsStore>(_ => new FakeSettingsStore());
|
||||||
services.AddScoped<ISecretCipher>(_ => new FakeSecretCipher());
|
services.AddScoped<ISecretCipher>(_ => TestCiphers.New());
|
||||||
services.AddScoped<ICardStore>(_ => new FakeKanjStore());
|
services.AddScoped<ICardStore>(_ => new FakeKanjStore());
|
||||||
services.AddScoped<IMlLearningStore>(_ => new FakeMlLearningStore());
|
services.AddScoped<IMlLearningStore>(_ => new FakeMlLearningStore());
|
||||||
services.AddScoped<ITenantLimitStore>(_ => new FakeTenantLimitStore());
|
services.AddScoped<ITenantLimitStore>(_ => new FakeTenantLimitStore());
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Deal.Modules.Kanban.Application.Models;
|
|||||||
using Deal.Modules.Pipeline.Application.Models;
|
using Deal.Modules.Pipeline.Application.Models;
|
||||||
using Deal.Modules.Pipeline.Application.Parse;
|
using Deal.Modules.Pipeline.Application.Parse;
|
||||||
using Deal.Modules.Pipeline.Application.Services;
|
using Deal.Modules.Pipeline.Application.Services;
|
||||||
|
using Deal.Modules.Settings.Application.Abstractions;
|
||||||
using Deal.Modules.Settings.Application.Models;
|
using Deal.Modules.Settings.Application.Models;
|
||||||
using Deal.Modules.Tenants.Application.Models;
|
using Deal.Modules.Tenants.Application.Models;
|
||||||
using Deal.Modules.Tenants.Application.Services;
|
using Deal.Modules.Tenants.Application.Services;
|
||||||
@@ -178,7 +179,7 @@ public sealed class PipelineWorkerGrpcAiTests
|
|||||||
var grpcClassifier = new GrpcAiClassifier(
|
var grpcClassifier = new GrpcAiClassifier(
|
||||||
tenantContext,
|
tenantContext,
|
||||||
connection,
|
connection,
|
||||||
new AiProviderConfigBuilder(settings, new FakeSecretCipher()),
|
new AiProviderConfigBuilder(settings, TestCiphers.New()),
|
||||||
new AiClassifyContextBuilder(settings, kanjStore),
|
new AiClassifyContextBuilder(settings, kanjStore),
|
||||||
new TokenUsageRecorder(settings, limits, tenantContext, new TokenUsageEventService(new FakeTokenUsageEventStore())),
|
new TokenUsageRecorder(settings, limits, tenantContext, new TokenUsageEventService(new FakeTokenUsageEventStore())),
|
||||||
NullLogger<GrpcAiClassifier>.Instance);
|
NullLogger<GrpcAiClassifier>.Instance);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Deal.Tests.Unit.Support;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using Deal.Contracts.Integrations.Models;
|
using Deal.Contracts.Integrations.Models;
|
||||||
@@ -9,6 +10,7 @@ using Deal.Infrastructure.Integrations.Options;
|
|||||||
using Deal.Infrastructure.Integrations.Services;
|
using Deal.Infrastructure.Integrations.Services;
|
||||||
using Deal.Modules.Kanban.Application.Models;
|
using Deal.Modules.Kanban.Application.Models;
|
||||||
using Deal.Modules.Pipeline.Application.Services;
|
using Deal.Modules.Pipeline.Application.Services;
|
||||||
|
using Deal.Modules.Settings.Application.Abstractions;
|
||||||
using Deal.Modules.Settings.Application.Models;
|
using Deal.Modules.Settings.Application.Models;
|
||||||
using Deal.Modules.Tenants.Application.Services;
|
using Deal.Modules.Tenants.Application.Services;
|
||||||
using Deal.SharedKernel.Tenants.Models;
|
using Deal.SharedKernel.Tenants.Models;
|
||||||
@@ -50,7 +52,7 @@ public sealed class GrpcAiClassifierTests
|
|||||||
Reason = "реклама",
|
Reason = "реклама",
|
||||||
Usage = new Usage { Prompt = 500, Completion = 40, Total = 540 },
|
Usage = new Usage { Prompt = 500, Completion = 40, Total = 540 },
|
||||||
};
|
};
|
||||||
(FakeSettingsStore settings, FakeSecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
(FakeSettingsStore settings, ISecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
||||||
settings.Preload(SettingsKeys.AiFilterPrompt, Json("Фильтруй. {domain} | {keywords}."));
|
settings.Preload(SettingsKeys.AiFilterPrompt, Json("Фильтруй. {domain} | {keywords}."));
|
||||||
settings.Preload(SettingsKeys.DomainDescription, Json(TestDomain));
|
settings.Preload(SettingsKeys.DomainDescription, Json(TestDomain));
|
||||||
settings.Preload(SettingsKeys.DomainKeywords, Json(TestKeywords));
|
settings.Preload(SettingsKeys.DomainKeywords, Json(TestKeywords));
|
||||||
@@ -86,7 +88,7 @@ public sealed class GrpcAiClassifierTests
|
|||||||
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
||||||
{
|
{
|
||||||
service.FilterUnavailable = true;
|
service.FilterUnavailable = true;
|
||||||
(FakeSettingsStore settings, FakeSecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
(FakeSettingsStore settings, ISecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
||||||
IAiClassifier classifier = CreateClassifier(port, settings, cipher, kanjStore);
|
IAiClassifier classifier = CreateClassifier(port, settings, cipher, kanjStore);
|
||||||
|
|
||||||
await Assert.ThrowsAsync<AiUnavailableException>(
|
await Assert.ThrowsAsync<AiUnavailableException>(
|
||||||
@@ -100,7 +102,7 @@ public sealed class GrpcAiClassifierTests
|
|||||||
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
||||||
{
|
{
|
||||||
string text = new string('а', 5000);
|
string text = new string('а', 5000);
|
||||||
(FakeSettingsStore settings, FakeSecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
(FakeSettingsStore settings, ISecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
||||||
IAiClassifier classifier = CreateClassifier(port, settings, cipher, kanjStore);
|
IAiClassifier classifier = CreateClassifier(port, settings, cipher, kanjStore);
|
||||||
|
|
||||||
await classifier.FilterAsync(text, CancellationToken.None);
|
await classifier.FilterAsync(text, CancellationToken.None);
|
||||||
@@ -129,7 +131,7 @@ public sealed class GrpcAiClassifierTests
|
|||||||
""",
|
""",
|
||||||
Usage = new Usage { Prompt = 3000, Completion = 700, Total = 3700 },
|
Usage = new Usage { Prompt = 3000, Completion = 700, Total = 3700 },
|
||||||
};
|
};
|
||||||
(FakeSettingsStore settings, FakeSecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
(FakeSettingsStore settings, ISecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
||||||
settings.Preload(SettingsKeys.AiPrompt, Json("Разбор: {domain}."));
|
settings.Preload(SettingsKeys.AiPrompt, Json("Разбор: {domain}."));
|
||||||
settings.Preload(SettingsKeys.CardPrompt, Json("Верни блок «О заявке»."));
|
settings.Preload(SettingsKeys.CardPrompt, Json("Верни блок «О заявке»."));
|
||||||
settings.Preload(SettingsKeys.DomainDescription, Json(TestDomain));
|
settings.Preload(SettingsKeys.DomainDescription, Json(TestDomain));
|
||||||
@@ -189,7 +191,7 @@ public sealed class GrpcAiClassifierTests
|
|||||||
Ok = false,
|
Ok = false,
|
||||||
Usage = new Usage { Prompt = 900, Completion = 0, Total = 900 },
|
Usage = new Usage { Prompt = 900, Completion = 0, Total = 900 },
|
||||||
};
|
};
|
||||||
(FakeSettingsStore settings, FakeSecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
(FakeSettingsStore settings, ISecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
||||||
|
|
||||||
FakeTenantLimitStore limits = new();
|
FakeTenantLimitStore limits = new();
|
||||||
IAiClassifier classifier = CreateClassifier(port, settings, cipher, kanjStore, limits);
|
IAiClassifier classifier = CreateClassifier(port, settings, cipher, kanjStore, limits);
|
||||||
@@ -210,7 +212,7 @@ public sealed class GrpcAiClassifierTests
|
|||||||
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
||||||
{
|
{
|
||||||
service.ClassifyUnavailable = true;
|
service.ClassifyUnavailable = true;
|
||||||
(FakeSettingsStore settings, FakeSecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
(FakeSettingsStore settings, ISecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
||||||
IAiClassifier classifier = CreateClassifier(port, settings, cipher, kanjStore);
|
IAiClassifier classifier = CreateClassifier(port, settings, cipher, kanjStore);
|
||||||
|
|
||||||
await Assert.ThrowsAsync<AiUnavailableException>(
|
await Assert.ThrowsAsync<AiUnavailableException>(
|
||||||
@@ -224,7 +226,7 @@ public sealed class GrpcAiClassifierTests
|
|||||||
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
await AiGrpcTestHost.RunAsync(AiGrpcTestHost.DefaultToken, new RecordingAiService(), async (port, service) =>
|
||||||
{
|
{
|
||||||
service.ClassifyReply = new ClassifyReply { Ok = true, Json = """{"title":"Заголовок","stack":[],"is_spam":false}""" };
|
service.ClassifyReply = new ClassifyReply { Ok = true, Json = """{"title":"Заголовок","stack":[],"is_spam":false}""" };
|
||||||
(FakeSettingsStore settings, FakeSecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
(FakeSettingsStore settings, ISecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
||||||
settings.Preload(SettingsKeys.AiProvider, Json("anthropic"));
|
settings.Preload(SettingsKeys.AiProvider, Json("anthropic"));
|
||||||
settings.Preload(
|
settings.Preload(
|
||||||
SettingsKeys.AiConfigs,
|
SettingsKeys.AiConfigs,
|
||||||
@@ -261,7 +263,7 @@ public sealed class GrpcAiClassifierTests
|
|||||||
{
|
{
|
||||||
service.ClassifyReply = new ClassifyReply { Ok = true, Json = """{"title":"Т","stack":[],"is_spam":false}""" };
|
service.ClassifyReply = new ClassifyReply { Ok = true, Json = """{"title":"Т","stack":[],"is_spam":false}""" };
|
||||||
string text = new string('б', 6000);
|
string text = new string('б', 6000);
|
||||||
(FakeSettingsStore settings, FakeSecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
(FakeSettingsStore settings, ISecretCipher cipher, FakeKanjStore kanjStore) = Context(port);
|
||||||
IAiClassifier classifier = CreateClassifier(port, settings, cipher, kanjStore);
|
IAiClassifier classifier = CreateClassifier(port, settings, cipher, kanjStore);
|
||||||
|
|
||||||
await classifier.ClassifyAsync(text, CancellationToken.None);
|
await classifier.ClassifyAsync(text, CancellationToken.None);
|
||||||
@@ -283,7 +285,7 @@ public sealed class GrpcAiClassifierTests
|
|||||||
private static GrpcAiClassifier CreateClassifier(
|
private static GrpcAiClassifier CreateClassifier(
|
||||||
int port,
|
int port,
|
||||||
FakeSettingsStore settings,
|
FakeSettingsStore settings,
|
||||||
FakeSecretCipher cipher,
|
ISecretCipher cipher,
|
||||||
FakeKanjStore kanjStore,
|
FakeKanjStore kanjStore,
|
||||||
FakeTenantLimitStore? limits = null)
|
FakeTenantLimitStore? limits = null)
|
||||||
{
|
{
|
||||||
@@ -302,8 +304,8 @@ public sealed class GrpcAiClassifierTests
|
|||||||
// Создаёт контекст сценария (пустые фейки; сценарий переопределяет настройки/доски).
|
// Создаёт контекст сценария (пустые фейки; сценарий переопределяет настройки/доски).
|
||||||
// port: Порт хоста (не используется контекстом — единый вид хелперов).
|
// port: Порт хоста (не используется контекстом — единый вид хелперов).
|
||||||
// Возвращает: Кортеж фейков (настройки, шифр, канбан).
|
// Возвращает: Кортеж фейков (настройки, шифр, канбан).
|
||||||
private static (FakeSettingsStore Settings, FakeSecretCipher Cipher, FakeKanjStore Kanj) Context(int port)
|
private static (FakeSettingsStore Settings, ISecretCipher Cipher, FakeKanjStore Kanj) Context(int port)
|
||||||
=> (new FakeSettingsStore(), new FakeSecretCipher(), new FakeKanjStore());
|
=> (new FakeSettingsStore(), TestCiphers.New(), new FakeKanjStore());
|
||||||
|
|
||||||
// Сериализует значение настройки в JSON-строку (как пишет SettingsStore).
|
// Сериализует значение настройки в JSON-строку (как пишет SettingsStore).
|
||||||
// value: Значение (строка/список).
|
// value: Значение (строка/список).
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ internal static class OperatorAuthHttpHost
|
|||||||
builder.Services.AddSingleton<ITenantRepository>(effectiveTenantStore);
|
builder.Services.AddSingleton<ITenantRepository>(effectiveTenantStore);
|
||||||
builder.Services.AddSingleton<ITenantLimitStore>(effectiveLimitStore);
|
builder.Services.AddSingleton<ITenantLimitStore>(effectiveLimitStore);
|
||||||
builder.Services.AddSingleton<IGlobalSettingsStore>(effectiveGlobalSettingsStore.Store);
|
builder.Services.AddSingleton<IGlobalSettingsStore>(effectiveGlobalSettingsStore.Store);
|
||||||
builder.Services.AddSingleton<ISecretCipher>(new FakeSecretCipher());
|
builder.Services.AddSingleton<ISecretCipher>(TestCiphers.New());
|
||||||
// Сервис глобальных ключей Telegram (операторские ручки /api/operator/settings/telegram-keys).
|
// Сервис глобальных ключей Telegram (операторские ручки /api/operator/settings/telegram-keys).
|
||||||
builder.Services.AddScoped<TelegramKeysService>();
|
builder.Services.AddScoped<TelegramKeysService>();
|
||||||
builder.Services.AddSingleton<ITokenUsageEventStore>(effectiveTokenUsageStore);
|
builder.Services.AddSingleton<ITokenUsageEventStore>(effectiveTokenUsageStore);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Deal.Tests.Unit.Support;
|
|||||||
public sealed class SettingsServiceTests
|
public sealed class SettingsServiceTests
|
||||||
{
|
{
|
||||||
private readonly FakeSettingsStore _store = new();
|
private readonly FakeSettingsStore _store = new();
|
||||||
private readonly FakeSecretCipher _cipher = new();
|
private readonly ISecretCipher _cipher = TestCiphers.New();
|
||||||
private readonly SettingsService _service;
|
private readonly SettingsService _service;
|
||||||
|
|
||||||
public SettingsServiceTests()
|
public SettingsServiceTests()
|
||||||
|
|||||||
+17
-6
@@ -1,17 +1,29 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Deal.Modules.Settings.Application.Abstractions;
|
using Deal.Modules.Settings.Application.Abstractions;
|
||||||
|
using NSubstitute;
|
||||||
|
|
||||||
namespace Deal.Tests.Unit.Support;
|
namespace Deal.Tests.Unit.Support;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Детерминированный шифр для тестов SettingsService
|
/// Подставка <see cref="ISecretCipher"/> с детерминированной семантикой «enc:base64»
|
||||||
|
/// (как раньше — фейк): шифрование/расшифровка взаимно обратимы в тестах.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class FakeSecretCipher : ISecretCipher
|
public static class TestCiphers
|
||||||
{
|
{
|
||||||
private const string EncryptedPrefix = "enc:";
|
private const string EncryptedPrefix = "enc:";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public string Encrypt(string plainText)
|
/// Создаёт подставку шифра.
|
||||||
|
/// </summary>
|
||||||
|
public static ISecretCipher New()
|
||||||
|
{
|
||||||
|
ISecretCipher cipher = Substitute.For<ISecretCipher>();
|
||||||
|
cipher.Encrypt(Arg.Any<string>()).Returns(ci => Encrypt(ci.ArgAt<string>(0)));
|
||||||
|
cipher.Decrypt(Arg.Any<string>()).Returns(ci => Decrypt(ci.ArgAt<string>(0)));
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Encrypt(string plainText)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(plainText))
|
if (string.IsNullOrEmpty(plainText))
|
||||||
{
|
{
|
||||||
@@ -21,8 +33,7 @@ public sealed class FakeSecretCipher : ISecretCipher
|
|||||||
return EncryptedPrefix + Convert.ToBase64String(Encoding.UTF8.GetBytes(plainText));
|
return EncryptedPrefix + Convert.ToBase64String(Encoding.UTF8.GetBytes(plainText));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
private static string Decrypt(string cipherText)
|
||||||
public string Decrypt(string cipherText)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(cipherText))
|
if (string.IsNullOrEmpty(cipherText))
|
||||||
{
|
{
|
||||||
Reference in New Issue
Block a user