using Deal.Grpc.Telegram; using Deal.Telegram.Core; using Deal.Telegram.Dialogs; using Grpc.Core; using Deal.Telegram.Tests.Support; namespace Deal.Telegram.Tests.Grpc; // Фейк исходящего канала в ядро (ICoreIngressClient) для unit-тестов служб каталога: записывает // PushMessage/SyncDialogs без сети; SyncDialogs возвращает настраиваемый monitored-набор (роль ядра, // Ruling 7). Проводка реального gRPC-канала проверяется отдельно — CoreIngressClientTests против // in-proc фейк-сервера ингресса (FakeIngressServer). internal sealed class FakeIngress : ICoreIngressClient { /// /// Отправленные PushMessage (tenant + запрос), в порядке вызовов. /// public List<(string TenantId, PushMessageRequest Message)> Pushes { get; } = []; /// /// Синхронизации каталога (tenant + entries), в порядке вызовов. /// public List<(string TenantId, IReadOnlyList Entries)> Syncs { get; } = []; /// /// Monitored-набор, который фейк возвращает ответом SyncDialogs (как ядро). /// public List MonitoredIdsToReturn { get; set; } = []; /// /// Ошибка PushMessageAsync (null — успех). /// public Exception? PushError { get; set; } /// /// Ошибка SyncDialogsAsync (null — успех). /// public Exception? SyncError { get; set; } /// /// Текст последнего PushMessage (для быстрых проверок). /// public string? LastPushText => Pushes.Count == 0 ? null : Pushes[^1].Message.Text; /// /// Id последнего отправленного диалога. /// public string? LastPushDialogId => Pushes.Count == 0 ? null : Pushes[^1].Message.DialogId; /// public Task PushMessageAsync( string tenantId, PushMessageRequest message, CancellationToken cancellationToken) { if (PushError is not null) { throw PushError; } Pushes.Add((tenantId, message)); return Task.FromResult(new PushMessageReply { Accepted = true, Duplicate = false }); } /// public Task> SyncDialogsAsync( string tenantId, IReadOnlyList entries, CancellationToken cancellationToken) { if (SyncError is not null) { throw SyncError; } Syncs.Add((tenantId, entries.ToArray())); return Task.FromResult>(MonitoredIdsToReturn.ToArray()); } } // Фейк анти-бан-пейсера: паузы не ждёт, а записывает запрошенные диапазоны (сек) — проверка // «паузы 1.5–3 с/сообщение, 3–6 с/диалог» без реальных задержек (fake clock плана Task 10). internal sealed class RecordingPacer : IBackfillPacer { /// /// Запрошенные диапазоны пауз (min/max, сек), в порядке вызовов. /// public List<(double MinSeconds, double MaxSeconds)> Waits { get; } = []; /// public Task WaitAsync( double minSeconds, double maxSeconds, CancellationToken cancellationToken) { Waits.Add((minSeconds, maxSeconds)); return Task.CompletedTask; } }