Files
Deal/src/telegram-service/Deal.Telegram.Tests/Grpc/TestDoubles.cs
T
Rustam Khalimov 5bfa92a4ac Сгруппировать тестовые проекты по областям
Deal.Tests.Unit, Deal.Telegram.Tests, Deal.Ai.Tests, Deal.Ml.Tests
разложены по областям (Modules/<X>, Api, Infrastructure, Contracts,
Grpc, ...), общие хелперы -> Support; namespace = папка, using
между областями добавлены итеративно по ошибкам сборки.
2026-09-11 13:28:26 +03:00

100 lines
3.8 KiB
C#

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
{
/// <summary>
/// Отправленные PushMessage (tenant + запрос), в порядке вызовов.
/// </summary>
public List<(string TenantId, PushMessageRequest Message)> Pushes { get; } = [];
/// <summary>
/// Синхронизации каталога (tenant + entries), в порядке вызовов.
/// </summary>
public List<(string TenantId, IReadOnlyList<DialogEntry> Entries)> Syncs { get; } = [];
/// <summary>
/// Monitored-набор, который фейк возвращает ответом SyncDialogs (как ядро).
/// </summary>
public List<string> MonitoredIdsToReturn { get; set; } = [];
/// <summary>
/// Ошибка PushMessageAsync (null — успех).
/// </summary>
public Exception? PushError { get; set; }
/// <summary>
/// Ошибка SyncDialogsAsync (null — успех).
/// </summary>
public Exception? SyncError { get; set; }
/// <summary>
/// Текст последнего PushMessage (для быстрых проверок).
/// </summary>
public string? LastPushText => Pushes.Count == 0 ? null : Pushes[^1].Message.Text;
/// <summary>
/// Id последнего отправленного диалога.
/// </summary>
public string? LastPushDialogId => Pushes.Count == 0 ? null : Pushes[^1].Message.DialogId;
/// <inheritdoc />
public Task<PushMessageReply> 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 });
}
/// <inheritdoc />
public Task<IReadOnlyList<string>> SyncDialogsAsync(
string tenantId,
IReadOnlyList<DialogEntry> entries,
CancellationToken cancellationToken)
{
if (SyncError is not null)
{
throw SyncError;
}
Syncs.Add((tenantId, entries.ToArray()));
return Task.FromResult<IReadOnlyList<string>>(MonitoredIdsToReturn.ToArray());
}
}
// Фейк анти-бан-пейсера: паузы не ждёт, а записывает запрошенные диапазоны (сек) — проверка
// «паузы 1.5–3 с/сообщение, 3–6 с/диалог» без реальных задержек (fake clock плана Task 10).
internal sealed class RecordingPacer : IBackfillPacer
{
/// <summary>
/// Запрошенные диапазоны пауз (min/max, сек), в порядке вызовов.
/// </summary>
public List<(double MinSeconds, double MaxSeconds)> Waits { get; } = [];
/// <inheritdoc />
public Task WaitAsync(
double minSeconds,
double maxSeconds,
CancellationToken cancellationToken)
{
Waits.Add((minSeconds, maxSeconds));
return Task.CompletedTask;
}
}