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

Первый фейк из чек-листа задачи #1: Substitute.For<IDiscoveryPacer>, счётчики
DelayCalls -> Received/DidNotReceive, колбэк OnDelay -> When/Do. Фейк удалён,
тесты 1340 зелёные.
This commit is contained in:
Rustam Khalimov
2026-09-12 00:07:27 +03:00
parent 27c7831910
commit c6332158fe
2 changed files with 10 additions and 34 deletions
@@ -1,9 +1,11 @@
using Deal.Contracts.Integrations.Models; using Deal.Contracts.Integrations.Models;
using Deal.Modules.Discovery.Application.Abstractions;
using Deal.Modules.Discovery.Application.Models; using Deal.Modules.Discovery.Application.Models;
using Deal.Tests.Unit.Modules.Discovery;
using Deal.Modules.Discovery.Application.Services; using Deal.Modules.Discovery.Application.Services;
using Deal.Modules.Settings.Application.Models; using Deal.Modules.Settings.Application.Models;
using Deal.Tests.Unit.Modules.Discovery;
using Deal.Tests.Unit.Modules.Settings; using Deal.Tests.Unit.Modules.Settings;
using NSubstitute;
using Grpc.Core; using Grpc.Core;
namespace Deal.Tests.Unit.Contracts; namespace Deal.Tests.Unit.Contracts;
@@ -263,7 +265,7 @@ public sealed class DiscoveryWorkerServiceTests
DiscoveryWorkerOutcome outcome = await fx.Worker.TickOnceAsync(CancellationToken.None); DiscoveryWorkerOutcome outcome = await fx.Worker.TickOnceAsync(CancellationToken.None);
Assert.Equal((DiscoveryWorkerService.ActionJoin, "dt_1"), (outcome.Action, outcome.TaskId)); Assert.Equal((DiscoveryWorkerService.ActionJoin, "dt_1"), (outcome.Action, outcome.TaskId));
Assert.Equal(1, fx.Pacer.DelayCalls); // пауза между вступлениями была (в проде 50–70 с) await fx.Pacer.Received(1).WaitJoinDelayAsync(Arg.Any<CancellationToken>()); // пауза между вступлениями была (в проде 50–70 с)
Assert.Equal(["grp1"], fx.Gateway.JoinedUsernames); Assert.Equal(["grp1"], fx.Gateway.JoinedUsernames);
DiscoveryCandidateDto candidate = fx.Store.Candidates.Single(); DiscoveryCandidateDto candidate = fx.Store.Candidates.Single();
Assert.Equal(DiscoveryCandidateStatuses.Joined, candidate.Status); Assert.Equal(DiscoveryCandidateStatuses.Joined, candidate.Status);
@@ -326,7 +328,7 @@ public sealed class DiscoveryWorkerServiceTests
Assert.Equal(DiscoveryWorkerService.ActionError, first.Action); Assert.Equal(DiscoveryWorkerService.ActionError, first.Action);
Assert.Equal(DiscoveryWorkerService.ActionError, second.Action); Assert.Equal(DiscoveryWorkerService.ActionError, second.Action);
Assert.Equal(DiscoveryWorkerService.ActionSkip, third.Action); // 3-я неудача — кандидат удаляется (python L417420) Assert.Equal(DiscoveryWorkerService.ActionSkip, third.Action); // 3-я неудача — кандидат удаляется (python L417420)
Assert.Equal(3, fx.Pacer.DelayCalls); await fx.Pacer.Received(3).WaitJoinDelayAsync(Arg.Any<CancellationToken>());
Assert.Empty(fx.Store.Candidates); Assert.Empty(fx.Store.Candidates);
Assert.Contains(fx.Store.Log, log => log.Event == DiscoveryLogEvents.Skip && log.Text.Contains("не удалось вступить (3 попытки)")); Assert.Contains(fx.Store.Log, log => log.Event == DiscoveryLogEvents.Skip && log.Text.Contains("не удалось вступить (3 попытки)"));
} }
@@ -338,7 +340,8 @@ public sealed class DiscoveryWorkerServiceTests
fx.Store.SeedTask(Task("dt_1", searchDone: true, autoJoin: true)); fx.Store.SeedTask(Task("dt_1", searchDone: true, autoJoin: true));
fx.Store.SeedCandidate(Candidate("d1", "dt_1", DiscoveryCandidateStatuses.Review, username: "grp1")); fx.Store.SeedCandidate(Candidate("d1", "dt_1", DiscoveryCandidateStatuses.Review, username: "grp1"));
// За «паузу» 50–70 с задачу поставили на паузу вручную: повторная проверка после паузы → выход без join. // За «паузу» 50–70 с задачу поставили на паузу вручную: повторная проверка после паузы → выход без join.
fx.Pacer.OnDelay = () => fx.Tasks.PauseAsync("dt_1", CancellationToken.None).GetAwaiter().GetResult(); fx.Pacer.When(x => x.WaitJoinDelayAsync(Arg.Any<CancellationToken>()))
.Do(_ => fx.Tasks.PauseAsync("dt_1", CancellationToken.None).GetAwaiter().GetResult());
DiscoveryWorkerOutcome outcome = await fx.Worker.TickOnceAsync(CancellationToken.None); DiscoveryWorkerOutcome outcome = await fx.Worker.TickOnceAsync(CancellationToken.None);
@@ -361,7 +364,7 @@ public sealed class DiscoveryWorkerServiceTests
Assert.Equal(DiscoveryWorkerService.ActionNone, outcome.Action); Assert.Equal(DiscoveryWorkerService.ActionNone, outcome.Action);
Assert.Empty(fx.Gateway.JoinedUsernames); Assert.Empty(fx.Gateway.JoinedUsernames);
Assert.Equal(0, fx.Pacer.DelayCalls); // join никому нельзя — пауза даже не запрашивалась (python L480481) await fx.Pacer.DidNotReceive().WaitJoinDelayAsync(Arg.Any<CancellationToken>()); // join никому нельзя — пауза даже не запрашивалась (python L480481)
} }
[Fact] [Fact]
@@ -417,7 +420,7 @@ public sealed class DiscoveryWorkerServiceTests
FakeDiscoveryStore Store, FakeDiscoveryStore Store,
FakeSettingsStore Settings, FakeSettingsStore Settings,
FakeDiscoveryGateway Gateway, FakeDiscoveryGateway Gateway,
FakeDiscoveryPacer Pacer, IDiscoveryPacer Pacer,
DiscoveryWorkerService Worker, DiscoveryWorkerService Worker,
DiscoveryTasksService Tasks); DiscoveryTasksService Tasks);
@@ -436,7 +439,7 @@ public sealed class DiscoveryWorkerServiceTests
var planGuard = new DiscoveryPlanGuard(store, settings); var planGuard = new DiscoveryPlanGuard(store, settings);
var tasks = new DiscoveryTasksService(store, planGuard, settings); var tasks = new DiscoveryTasksService(store, planGuard, settings);
var banGuard = new DiscoveryBanGuard(store, settings); var banGuard = new DiscoveryBanGuard(store, settings);
var pacer = new FakeDiscoveryPacer(); var pacer = Substitute.For<IDiscoveryPacer>();
var gateway = new FakeDiscoveryGateway(); var gateway = new FakeDiscoveryGateway();
var worker = new DiscoveryWorkerService( var worker = new DiscoveryWorkerService(
store, tasks, candidates, log, new DiscoveryEvaluator(settings, ml, ai), banGuard, pacer, gateway); store, tasks, candidates, log, new DiscoveryEvaluator(settings, ml, ai), banGuard, pacer, gateway);
@@ -1,27 +0,0 @@
using Deal.Modules.Discovery.Application.Abstractions;
namespace Deal.Tests.Unit.Modules.Discovery;
/// <summary>
/// Фейковый <see cref="IDiscoveryPacer"/> для тестов воркера Discovery
/// </summary>
public sealed class FakeDiscoveryPacer : IDiscoveryPacer
{
/// <summary>
/// Действие, выполняемое в момент паузы
/// </summary>
public Action? OnDelay { get; set; }
/// <summary>
/// Сколько раз запрошена пауза перед авто-вступлением
/// </summary>
public int DelayCalls { get; private set; }
/// <inheritdoc />
public Task WaitJoinDelayAsync(CancellationToken ct)
{
DelayCalls++;
OnDelay?.Invoke();
return Task.CompletedTask;
}
}