Deal.Tests.Unit, Deal.Telegram.Tests, Deal.Ai.Tests, Deal.Ml.Tests разложены по областям (Modules/<X>, Api, Infrastructure, Contracts, Grpc, ...), общие хелперы -> Support; namespace = папка, using между областями добавлены итеративно по ошибкам сборки.
100 lines
3.8 KiB
C#
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;
|
|
}
|
|
}
|