Перевести FakeTelegramStore на NSubstitute
ci / build-test (push) Canceled after 0s

Хелпер Support/TestTelegramStore: словари каталога/превью + NSubstitute-подставка,
все методы порта читают состояние на момент вызова (семантика sync/upsert/
мониторов/бэкфилла сохранена). Потребители (5 файлов) перетипизированы на
.Store, фейк удалён, тесты 1340 зелёные.
This commit is contained in:
Rustam Khalimov
2026-09-12 21:55:43 +03:00
parent 25954c9fad
commit 435c473df4
6 changed files with 114 additions and 127 deletions
@@ -42,7 +42,7 @@ public sealed class TgStatusServiceTests
[Fact]
public async Task GetAsync_GatewayUnavailable_ReturnsIdleFormWithKvAccountCountAndKeys()
{
(TgStatusService service, FakeTelegramStore store, FakeSettingsStore settings, TestTelegramGateway gateway, _, TestGlobalSettingsStore globalSettings) = Create();
(TgStatusService service, TestTelegramStore store, FakeSettingsStore settings, TestTelegramGateway gateway, _, TestGlobalSettingsStore globalSettings) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true));
store.Seed(Dialog("d_2", "Группа", "group", Monitor: true));
store.Seed(Dialog("d_off", "Выключен", "channel", Monitor: false));
@@ -68,7 +68,7 @@ public sealed class TgStatusServiceTests
[Fact]
public async Task GetAsync_ReadyGateway_ComposesLiveFieldsWithKvAccountMonitoredAndKeys()
{
(TgStatusService service, FakeTelegramStore store, FakeSettingsStore settings, TestTelegramGateway gateway, _, TestGlobalSettingsStore globalSettings) = Create();
(TgStatusService service, TestTelegramStore store, FakeSettingsStore settings, TestTelegramGateway gateway, _, TestGlobalSettingsStore globalSettings) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true));
settings.Preload(SettingsKeys.TgAccount, "\"@realuser\"");
PreloadKeys(globalSettings, "123456", "abcdefghijklmnop");
@@ -105,15 +105,15 @@ public sealed class TgStatusServiceTests
// ─── Хелперы ────────────────────────────────────────────────────────────
// Собирает сервис на фейках (FakeTelegramGateway/Store/SettingsStore/GlobalSettingsStore + FakeSecretCipher).
private static (TgStatusService Service, FakeTelegramStore Store, FakeSettingsStore Settings, TestTelegramGateway Gateway, FakeSecretCipher Cipher, TestGlobalSettingsStore GlobalSettings) Create()
private static (TgStatusService Service, TestTelegramStore Store, FakeSettingsStore Settings, TestTelegramGateway Gateway, FakeSecretCipher Cipher, TestGlobalSettingsStore GlobalSettings) Create()
{
var store = new FakeTelegramStore();
var store = new TestTelegramStore();
var settings = new FakeSettingsStore();
var gateway = new TestTelegramGateway();
var cipher = new FakeSecretCipher();
var globalSettings = new TestGlobalSettingsStore();
var keys = new TelegramKeysService(globalSettings.Store, cipher);
var dialogs = new DialogsService(store, settings, gateway.Gateway, NullLogger<DialogsService>.Instance);
var dialogs = new DialogsService(store.Store, settings, gateway.Gateway, NullLogger<DialogsService>.Instance);
var service = new TgStatusService(gateway.Gateway, dialogs, settings, keys);
return (service, store, settings, gateway, cipher, globalSettings);
}
@@ -21,7 +21,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task Sync_NewDialogsAutoMonitorNewDefaultTrue_TurnMonitoringOn()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
int count = await service.SyncFromTelegramAsync(Catalog(
Entry("d_1", "Канал", "channel"),
@@ -38,7 +38,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task Sync_AutoMonitorNewFalse_AddsNewDialogsUnmonitored()
{
(DialogsService service, FakeTelegramStore store, FakeSettingsStore settings, _) = Create();
(DialogsService service, TestTelegramStore store, FakeSettingsStore settings, _) = Create();
settings.Preload(SettingsKeys.AutoMonitorNew, "false");
int count = await service.SyncFromTelegramAsync(Catalog(Entry("d_1", "Канал", "channel")), CancellationToken.None);
@@ -55,7 +55,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task Sync_ExistingDialog_UpdatesMetadataAndKeepsUserMonitor()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
store.Seed(Dialog("d_1", "Старое имя", "channel", Monitor: false, Backfilled: true));
await service.SyncFromTelegramAsync(
@@ -76,7 +76,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task Sync_DialogLeftCatalog_IsDeleted()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
store.Seed(Dialog("d_left", "Ушёл", "channel", Monitor: true, Backfilled: true));
int count = await service.SyncFromTelegramAsync(Catalog(Entry("d_stay", "Остался", "group")), CancellationToken.None);
@@ -93,7 +93,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task Sync_EmptyCatalog_ReturnsZeroAndKeepsRows()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true, Backfilled: true));
int count = await service.SyncFromTelegramAsync([], CancellationToken.None);
@@ -110,7 +110,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task SetMonitor_EnableFirstTimeNotBackfilled_SetsFlagCallsGatewayAndNeedsBackfill()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: false, Backfilled: false));
TelegramMonitorToggleDto result = await service.SetMonitorAsync("d_1", enabled: true, CancellationToken.None);
@@ -127,7 +127,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task SetMonitor_EnableAlreadyBackfilled_NoBackfillNeeded()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: false, Backfilled: true));
TelegramMonitorToggleDto result = await service.SetMonitorAsync("d_1", enabled: true, CancellationToken.None);
@@ -143,7 +143,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task SetMonitor_Disable_TurnsOffAndNotifiesMirror()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true, Backfilled: true));
TelegramMonitorToggleDto result = await service.SetMonitorAsync("d_1", enabled: false, CancellationToken.None);
@@ -160,7 +160,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task SetMonitor_UnknownDialog_NoOpWithoutGatewayCall()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
TelegramMonitorToggleDto result = await service.SetMonitorAsync("missing", enabled: true, CancellationToken.None);
@@ -178,7 +178,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task SetMonitorAll_Enable_ReturnsCountAndNotBackfilledIdsAndNotifiesMirror()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_a", "А", "channel", Monitor: false, Backfilled: true));
store.Seed(Dialog("d_b", "Б", "channel", Monitor: false, Backfilled: false));
store.Seed(Dialog("d_c", "В", "group", Monitor: false, Backfilled: false));
@@ -197,7 +197,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task SetMonitorAll_Disable_TurnsAllOffAndEmptyBackfillList()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_a", "А", "channel", Monitor: true, Backfilled: false));
store.Seed(Dialog("d_b", "Б", "channel", Monitor: true, Backfilled: true));
@@ -215,7 +215,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task MarkBackfilled_SetsFlagOnDialog()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true, Backfilled: false));
await service.MarkBackfilledAsync("d_1", CancellationToken.None);
@@ -230,7 +230,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task ReadRecent_OneDialogFails_ContinuesWithOthersAndMarksOnlySuccessful()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true, Backfilled: false));
store.Seed(Dialog("d_2", "Группа", "group", Monitor: true, Backfilled: false));
store.Seed(Dialog("d_3", "Форум", "forum", Monitor: true, Backfilled: false));
@@ -251,7 +251,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task BackfillOne_UnknownOrAlreadyBackfilledWithoutForce_ReturnsZeroWithoutRpc()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_known", "Канал", "channel", Monitor: true, Backfilled: true));
store.Seed(Dialog("d_fresh", "Группа", "group", Monitor: false, Backfilled: false));
@@ -272,7 +272,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task BackfillOne_ForceTrue_BackfillsAlreadyBackfilledDialogAndMarksAgain()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true, Backfilled: true));
int processed = await service.BackfillOneAsync("d_1", force: true, CancellationToken.None);
@@ -290,7 +290,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task ReadRecent_OnlyMonitoredDialogs_BackfillsEachMonitoredWithForce()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true, Backfilled: true));
store.Seed(Dialog("d_2", "Группа", "group", Monitor: true, Backfilled: false));
store.Seed(Dialog("d_off", "Выключен", "channel", Monitor: false, Backfilled: false));
@@ -309,7 +309,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task ReadRecent_NoMonitoredDialogs_ReturnsZeroWithoutGatewayCalls()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_off", "Выключен", "channel", Monitor: false, Backfilled: false));
int count = await service.ReadRecentAsync(CancellationToken.None);
@@ -326,7 +326,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task List_ReturnsMonitoredFirstByNameOrderWithLastMessage()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
DateTimeOffset lastAt = DateTimeOffset.UtcNow.AddMinutes(-5);
store.Seed(Dialog("d_gamma", "Гамма", "channel", Monitor: true, Backfilled: true, LastText: "привет", LastAt: lastAt));
store.Seed(Dialog("d_alpha", "Альфа", "group", Monitor: false, Backfilled: false));
@@ -352,7 +352,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task SavePreview_PersistsPreviewRowAndTouchesDialogLast()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true, Backfilled: false));
string longText = new('т', 5000);
DateTimeOffset msgAt = DateTimeOffset.UtcNow.AddMinutes(-1);
@@ -375,7 +375,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task SavePreview_EmptyText_DoesNotTouchStore()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true, Backfilled: false));
await service.SavePreviewAsync("d_1", msgId: 7, " ", DateTimeOffset.UtcNow, CancellationToken.None);
@@ -391,7 +391,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task SavePreview_NoMsgId_UpdatesLastOnly()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true, Backfilled: false));
await service.SavePreviewAsync("d_1", msgId: null, "Новое сообщение", null, CancellationToken.None);
@@ -407,7 +407,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task SavePreview_DuplicateMessageId_KeepsFirstRow()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true, Backfilled: false));
DateTimeOffset firstAt = DateTimeOffset.UtcNow.AddMinutes(-2);
@@ -426,7 +426,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task AddDiscoveredMonitored_NewDialog_CreatesMonitoredRowAndNotifiesMirror()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
await service.AddDiscoveredMonitoredAsync("d_100", "Канал", "channel_handle", "channel", "#a33", CancellationToken.None);
@@ -447,7 +447,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task AddDiscoveredMonitored_ExistingDialog_UpdatesMetadataAndForcesMonitorOn()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
store.Seed(Dialog("d_100", "Старое имя", "group", Monitor: false, Backfilled: true));
await service.AddDiscoveredMonitoredAsync("d_100", "Новое имя", "new_handle", "channel", "#c21", CancellationToken.None);
@@ -468,7 +468,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task AddDiscoveredMonitored_EmptyNameAndHue_FallBackToDialogIdAndDefaultHue()
{
(DialogsService service, FakeTelegramStore store, _, _) = Create();
(DialogsService service, TestTelegramStore store, _, _) = Create();
await service.AddDiscoveredMonitoredAsync("d_100", " ", string.Empty, string.Empty, string.Empty, CancellationToken.None);
@@ -485,7 +485,7 @@ public sealed class DialogsServiceTests
[Fact]
public async Task AddDiscoveredMonitored_GatewayMirrorFails_RowStillWritten()
{
(DialogsService service, FakeTelegramStore store, _, TestTelegramGateway gateway) = Create();
(DialogsService service, TestTelegramStore store, _, TestTelegramGateway gateway) = Create();
gateway.SetMonitorFailures.Add("d_100");
await service.AddDiscoveredMonitoredAsync("d_100", "Канал", string.Empty, "channel", "#666", CancellationToken.None);
@@ -496,13 +496,13 @@ public sealed class DialogsServiceTests
// ─── Хелперы ────────────────────────────────────────────────────────────
// Собирает сервис на фейках (FakeTelegramStore — семантика адаптера; настройки/гейт чистые).
private static (DialogsService Service, FakeTelegramStore Store, FakeSettingsStore Settings, TestTelegramGateway Gateway) Create()
// Собирает сервис на фейках (TestTelegramStore — семантика адаптера; настройки/гейт чистые).
private static (DialogsService Service, TestTelegramStore Store, FakeSettingsStore Settings, TestTelegramGateway Gateway) Create()
{
var store = new FakeTelegramStore();
var store = new TestTelegramStore();
var settings = new FakeSettingsStore();
var gateway = new TestTelegramGateway();
return (new DialogsService(store, settings, gateway.Gateway, NullLogger<DialogsService>.Instance), store, settings, gateway);
return (new DialogsService(store.Store, settings, gateway.Gateway, NullLogger<DialogsService>.Instance), store, settings, gateway);
}
// Строка каталога минимально (id/имя/kind + флаги/хвост каталога).
@@ -35,11 +35,11 @@ public sealed class TelegramIngressServiceTests
public async Task SyncDialogs_KnownTenant_AppliesCatalogAndReturnsMonitoredIds()
{
var registry = TestRegistry.With(Tenant(TenantA));
var dialogs = new FakeTelegramStore();
var dialogs = new TestTelegramStore();
await RunAsync(
registry,
services => services.AddScoped<ITelegramStore>(_ => dialogs),
services => services.AddScoped<ITelegramStore>(_ => dialogs.Store),
async channel =>
{
var request = new SyncDialogsRequest();
@@ -63,14 +63,14 @@ public sealed class TelegramIngressServiceTests
var registry = TestRegistry.With(Tenant(TenantA));
var settings = new FakeSettingsStore();
settings.Preload(SettingsKeys.AutoMonitorNew, "false");
var dialogs = new FakeTelegramStore();
var dialogs = new TestTelegramStore();
await RunAsync(
registry,
services =>
{
services.AddScoped<ISettingsStore>(_ => settings);
services.AddScoped<ITelegramStore>(_ => dialogs);
services.AddScoped<ITelegramStore>(_ => dialogs.Store);
},
async channel =>
{
@@ -100,7 +100,7 @@ internal static class TelegramIngressTestHost
}
builder.Services.AddScoped<ISettingsStore>(_ => new FakeSettingsStore());
builder.Services.AddScoped<ITelegramStore>(_ => new FakeTelegramStore());
builder.Services.AddScoped<ITelegramStore>(_ => new TestTelegramStore().Store);
builder.Services.AddScoped<ITelegramGateway>(_ => new TestTelegramGateway().Gateway);
builder.Services.AddTelegramModule();
@@ -20,7 +20,7 @@ public sealed class TelegramSourceIngestObserverTests
[Fact]
public async Task OnIngestedAsync_KnownKind_SavesPreview()
{
(TelegramSourceIngestObserver observer, FakeTelegramStore store) = Create();
(TelegramSourceIngestObserver observer, TestTelegramStore store) = Create();
store.Seed(Dialog());
DateTimeOffset receivedAt = DateTimeOffset.UtcNow.AddMinutes(-1);
@@ -40,7 +40,7 @@ public sealed class TelegramSourceIngestObserverTests
[Fact]
public async Task OnIngestedAsync_ForeignKind_DoesNotTouchStore()
{
(TelegramSourceIngestObserver observer, FakeTelegramStore store) = Create();
(TelegramSourceIngestObserver observer, TestTelegramStore store) = Create();
store.Seed(Dialog());
await observer.OnIngestedAsync(Item("avito", DialogId, "7", "Текст", DateTimeOffset.UtcNow), CancellationToken.None);
@@ -56,7 +56,7 @@ public sealed class TelegramSourceIngestObserverTests
[Fact]
public async Task OnIngestedAsync_NoOriginRef_DoesNotTouchStore()
{
(TelegramSourceIngestObserver observer, FakeTelegramStore store) = Create();
(TelegramSourceIngestObserver observer, TestTelegramStore store) = Create();
store.Seed(Dialog());
await observer.OnIngestedAsync(Item("telegram", null, "7", "Текст", DateTimeOffset.UtcNow), CancellationToken.None);
@@ -66,11 +66,11 @@ public sealed class TelegramSourceIngestObserverTests
}
// Собирает наблюдателя на фейковом хранилище диалогов.
private static (TelegramSourceIngestObserver Observer, FakeTelegramStore Store) Create()
private static (TelegramSourceIngestObserver Observer, TestTelegramStore Store) Create()
{
var store = new FakeTelegramStore();
var store = new TestTelegramStore();
var dialogs = new DialogsService(
store,
store.Store,
new FakeSettingsStore(),
new TestTelegramGateway().Gateway,
NullLogger<DialogsService>.Instance);
@@ -1,28 +1,70 @@
using Deal.Contracts.Integrations.Models;
using Deal.Modules.Telegram.Application;
using Deal.Modules.Telegram.Application.Models;
using Deal.Tests.Unit.Support;
using NSubstitute;
namespace Deal.Tests.Unit.Contracts;
namespace Deal.Tests.Unit.Support;
/// <summary>
/// In-memory реализация <see cref="ITelegramStore"/> для юнит-тестов DialogsService/ингресса.
/// Подставка <see cref="ITelegramStore"/> на словарях: сервисы получают NSubstitute-подставку
/// (<see cref="Store"/>), тесты сеют данные через <see cref="Seed"/>/<see cref="SeedMessage"/>
/// и читают состояние через <see cref="Dialogs"/>/<see cref="Messages"/>.
/// </summary>
public sealed class FakeTelegramStore : ITelegramStore
public sealed class TestTelegramStore
{
private readonly Dictionary<string, FakeTelegramDialogRow> _dialogs = new(StringComparer.Ordinal);
private readonly Dictionary<string, FakeTelegramMessageRow> _messages = new(StringComparer.Ordinal);
/// <summary>
/// Строки каталога фейка
/// Подставка порта Telegram-хранилища (создаётся в конструкторе).
/// </summary>
public ITelegramStore Store { get; }
/// <summary>
/// Строки каталога подставки
/// </summary>
public IReadOnlyList<FakeTelegramDialogRow> Dialogs => _dialogs.Values.ToList();
/// <summary>
/// Строки превью фейка
/// Строки превью подставки
/// </summary>
public IReadOnlyList<FakeTelegramMessageRow> Messages => _messages.Values.ToList();
/// <summary>
/// Создаёт подставку с пустыми словарями.
/// </summary>
public TestTelegramStore()
{
Store = Substitute.For<ITelegramStore>();
Store.SyncFromTelegramAsync(Arg.Any<IReadOnlyCollection<TelegramDialogEntryDto>>(), Arg.Any<bool>(), Arg.Any<CancellationToken>())
.Returns(ci => SyncFromTelegram(
ci.ArgAt<IReadOnlyCollection<TelegramDialogEntryDto>>(0), ci.ArgAt<bool>(1)));
Store.ListAsync(Arg.Any<CancellationToken>()).Returns(ci => ListDialogs());
Store.ListMonitoredIdsAsync(Arg.Any<CancellationToken>()).Returns(ci => ListMonitoredIds());
Store.When(s => s.SetMonitorAsync(Arg.Any<string>(), Arg.Any<bool>(), Arg.Any<CancellationToken>()))
.Do(ci => SetMonitor(ci.ArgAt<string>(0), ci.ArgAt<bool>(1)));
Store.SetMonitorAllAsync(Arg.Any<bool>(), Arg.Any<CancellationToken>())
.Returns(ci => SetMonitorAll(ci.ArgAt<bool>(0)));
Store.GetBackfilledAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(ci => GetBackfilled(ci.ArgAt<string>(0)));
Store.ListNotBackfilledIdsAsync(Arg.Any<CancellationToken>()).Returns(ci => ListNotBackfilledIds());
Store.When(s => s.SetBackfilledAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()))
.Do(ci => SetBackfilled(ci.ArgAt<string>(0)));
Store.When(s => s.UpsertDiscoveredMonitoredAsync(
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>()))
.Do(ci => UpsertDiscoveredMonitored(
ci.ArgAt<string>(0), ci.ArgAt<string>(1), ci.ArgAt<string>(2), ci.ArgAt<string>(3), ci.ArgAt<string>(4)));
Store.When(s => s.SavePreviewAsync(
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTimeOffset>(), Arg.Any<CancellationToken>()))
.Do(ci => SavePreview(
ci.ArgAt<string>(0), ci.ArgAt<string>(1), ci.ArgAt<string>(2), ci.ArgAt<DateTimeOffset>(3)));
Store.When(s => s.TouchDialogLastAsync(
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTimeOffset>(), Arg.Any<CancellationToken>()))
.Do(ci => TouchDialogLast(ci.ArgAt<string>(0), ci.ArgAt<string>(1), ci.ArgAt<DateTimeOffset>(2)));
Store.ListMessagesAsync(Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
.Returns(ci => ListMessages(ci.ArgAt<string>(0), ci.ArgAt<int>(1)));
}
/// <summary>
/// Кладёт строку каталога напрямую
/// </summary>
@@ -41,15 +83,11 @@ public sealed class FakeTelegramStore : ITelegramStore
_messages[row.Id] = row;
}
/// <inheritdoc />
public Task<int> SyncFromTelegramAsync(
IReadOnlyCollection<TelegramDialogEntryDto> entries,
bool autoMonitorNew,
CancellationToken ct)
private int SyncFromTelegram(IReadOnlyCollection<TelegramDialogEntryDto> entries, bool autoMonitorNew)
{
if (entries.Count == 0)
{
return Task.FromResult(0);
return 0;
}
DateTimeOffset now = DateTimeOffset.UtcNow;
@@ -81,47 +119,36 @@ public sealed class FakeTelegramStore : ITelegramStore
_dialogs.Remove(id);
}
return Task.FromResult(entries.Count);
return entries.Count;
}
/// <inheritdoc />
public Task<IReadOnlyList<TelegramDialogDto>> ListAsync(CancellationToken ct)
private IReadOnlyList<TelegramDialogDto> ListDialogs()
{
IReadOnlyList<TelegramDialogDto> items = _dialogs.Values
return _dialogs.Values
.OrderByDescending(row => row.Monitor)
.ThenBy(row => row.Name)
.Select(ToDialogDto)
.ToList();
return Task.FromResult(items);
}
/// <inheritdoc />
public Task<IReadOnlyCollection<string>> ListMonitoredIdsAsync(CancellationToken ct)
private IReadOnlyCollection<string> ListMonitoredIds()
{
IReadOnlyCollection<string> ids = _dialogs.Values
return _dialogs.Values
.Where(row => row.Monitor)
.OrderBy(row => row.Id)
.Select(row => row.Id)
.ToList();
return Task.FromResult(ids);
}
/// <inheritdoc />
public Task SetMonitorAsync(
string dialogId,
bool enabled,
CancellationToken ct)
private void SetMonitor(string dialogId, bool enabled)
{
if (_dialogs.TryGetValue(dialogId, out FakeTelegramDialogRow? existing))
{
_dialogs[dialogId] = existing with { Monitor = enabled, UpdatedAt = DateTimeOffset.UtcNow };
}
return Task.CompletedTask;
}
/// <inheritdoc />
public Task<int> SetMonitorAllAsync(bool enabled, CancellationToken ct)
private int SetMonitorAll(bool enabled)
{
int count = _dialogs.Count;
DateTimeOffset now = DateTimeOffset.UtcNow;
@@ -130,50 +157,32 @@ public sealed class FakeTelegramStore : ITelegramStore
_dialogs[id] = _dialogs[id] with { Monitor = enabled, UpdatedAt = now };
}
return Task.FromResult(count);
return count;
}
/// <inheritdoc />
public Task<bool?> GetBackfilledAsync(string dialogId, CancellationToken ct)
private bool? GetBackfilled(string dialogId)
{
if (_dialogs.TryGetValue(dialogId, out FakeTelegramDialogRow? row))
{
return Task.FromResult<bool?>(row.Backfilled);
}
return Task.FromResult<bool?>(null);
return _dialogs.TryGetValue(dialogId, out FakeTelegramDialogRow? row) ? row.Backfilled : null;
}
/// <inheritdoc />
public Task<IReadOnlyCollection<string>> ListNotBackfilledIdsAsync(CancellationToken ct)
private IReadOnlyCollection<string> ListNotBackfilledIds()
{
IReadOnlyCollection<string> ids = _dialogs.Values
return _dialogs.Values
.Where(row => !row.Backfilled)
.OrderBy(row => row.Id)
.Select(row => row.Id)
.ToList();
return Task.FromResult(ids);
}
/// <inheritdoc />
public Task SetBackfilledAsync(string dialogId, CancellationToken ct)
private void SetBackfilled(string dialogId)
{
if (_dialogs.TryGetValue(dialogId, out FakeTelegramDialogRow? existing))
{
_dialogs[dialogId] = existing with { Backfilled = true };
}
return Task.CompletedTask;
}
/// <inheritdoc />
public Task UpsertDiscoveredMonitoredAsync(
string dialogId,
string name,
string handle,
string kind,
string hue,
CancellationToken ct)
private void UpsertDiscoveredMonitored(string dialogId, string name, string handle, string kind, string hue)
{
if (_dialogs.TryGetValue(dialogId, out FakeTelegramDialogRow? existing))
{
@@ -194,54 +203,32 @@ public sealed class FakeTelegramStore : ITelegramStore
dialogId, name, handle, kind, hue,
Monitor: true, LastText: string.Empty, LastAt: null, Backfilled: false, UpdatedAt: DateTimeOffset.UtcNow);
}
return Task.CompletedTask;
}
/// <inheritdoc />
public Task SavePreviewAsync(
string messageId,
string dialogId,
string text,
DateTimeOffset msgAt,
CancellationToken ct)
private void SavePreview(string messageId, string dialogId, string text, DateTimeOffset msgAt)
{
if (!_messages.ContainsKey(messageId))
{
_messages[messageId] = new FakeTelegramMessageRow(messageId, dialogId, text, msgAt, LeadId: null);
}
return Task.CompletedTask;
}
/// <inheritdoc />
public Task TouchDialogLastAsync(
string dialogId,
string text,
DateTimeOffset at,
CancellationToken ct)
private void TouchDialogLast(string dialogId, string text, DateTimeOffset at)
{
if (_dialogs.TryGetValue(dialogId, out FakeTelegramDialogRow? existing))
{
_dialogs[dialogId] = existing with { LastText = text, LastAt = at, UpdatedAt = at };
}
return Task.CompletedTask;
}
/// <inheritdoc />
public Task<IReadOnlyList<TelegramMessageDto>> ListMessagesAsync(
string dialogId,
int limit,
CancellationToken ct)
private IReadOnlyList<TelegramMessageDto> ListMessages(string dialogId, int limit)
{
IReadOnlyList<TelegramMessageDto> items = _messages.Values
return _messages.Values
.Where(row => row.DialogId == dialogId)
.OrderByDescending(row => row.MsgAt)
.Take(limit)
.Select(row => new TelegramMessageDto(row.Id, row.Text, row.MsgAt.ToUnixTimeMilliseconds(), row.LeadId is not null))
.ToList();
return Task.FromResult(items);
}
private static TelegramDialogDto ToDialogDto(FakeTelegramDialogRow row)