From c6332158fe4f8686bf6fedc947b884a1486ec672 Mon Sep 17 00:00:00 2001 From: Rustam Khalimov Date: Sat, 12 Sep 2026 00:07:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B2=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20FakeDiscoveryPacer=20=D0=BD=D0=B0=20NSubstitute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Первый фейк из чек-листа задачи #1: Substitute.For, счётчики DelayCalls -> Received/DidNotReceive, колбэк OnDelay -> When/Do. Фейк удалён, тесты 1340 зелёные. --- .../Contracts/DiscoveryWorkerServiceTests.cs | 17 +++++++----- .../Modules/Discovery/FakeDiscoveryPacer.cs | 27 ------------------- 2 files changed, 10 insertions(+), 34 deletions(-) delete mode 100644 src/core/tests/Deal.Tests.Unit/Modules/Discovery/FakeDiscoveryPacer.cs diff --git a/src/core/tests/Deal.Tests.Unit/Contracts/DiscoveryWorkerServiceTests.cs b/src/core/tests/Deal.Tests.Unit/Contracts/DiscoveryWorkerServiceTests.cs index ff1c28e..b169ce5 100644 --- a/src/core/tests/Deal.Tests.Unit/Contracts/DiscoveryWorkerServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/Contracts/DiscoveryWorkerServiceTests.cs @@ -1,9 +1,11 @@ using Deal.Contracts.Integrations.Models; +using Deal.Modules.Discovery.Application.Abstractions; using Deal.Modules.Discovery.Application.Models; +using Deal.Tests.Unit.Modules.Discovery; using Deal.Modules.Discovery.Application.Services; using Deal.Modules.Settings.Application.Models; -using Deal.Tests.Unit.Modules.Discovery; using Deal.Tests.Unit.Modules.Settings; +using NSubstitute; using Grpc.Core; namespace Deal.Tests.Unit.Contracts; @@ -263,7 +265,7 @@ public sealed class DiscoveryWorkerServiceTests DiscoveryWorkerOutcome outcome = await fx.Worker.TickOnceAsync(CancellationToken.None); 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()); // пауза между вступлениями была (в проде 50–70 с) Assert.Equal(["grp1"], fx.Gateway.JoinedUsernames); DiscoveryCandidateDto candidate = fx.Store.Candidates.Single(); Assert.Equal(DiscoveryCandidateStatuses.Joined, candidate.Status); @@ -326,7 +328,7 @@ public sealed class DiscoveryWorkerServiceTests Assert.Equal(DiscoveryWorkerService.ActionError, first.Action); Assert.Equal(DiscoveryWorkerService.ActionError, second.Action); Assert.Equal(DiscoveryWorkerService.ActionSkip, third.Action); // 3-я неудача — кандидат удаляется (python L417–420) - Assert.Equal(3, fx.Pacer.DelayCalls); + await fx.Pacer.Received(3).WaitJoinDelayAsync(Arg.Any()); Assert.Empty(fx.Store.Candidates); 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.SeedCandidate(Candidate("d1", "dt_1", DiscoveryCandidateStatuses.Review, username: "grp1")); // За «паузу» 50–70 с задачу поставили на паузу вручную: повторная проверка после паузы → выход без join. - fx.Pacer.OnDelay = () => fx.Tasks.PauseAsync("dt_1", CancellationToken.None).GetAwaiter().GetResult(); + fx.Pacer.When(x => x.WaitJoinDelayAsync(Arg.Any())) + .Do(_ => fx.Tasks.PauseAsync("dt_1", CancellationToken.None).GetAwaiter().GetResult()); DiscoveryWorkerOutcome outcome = await fx.Worker.TickOnceAsync(CancellationToken.None); @@ -361,7 +364,7 @@ public sealed class DiscoveryWorkerServiceTests Assert.Equal(DiscoveryWorkerService.ActionNone, outcome.Action); Assert.Empty(fx.Gateway.JoinedUsernames); - Assert.Equal(0, fx.Pacer.DelayCalls); // join никому нельзя — пауза даже не запрашивалась (python L480–481) + await fx.Pacer.DidNotReceive().WaitJoinDelayAsync(Arg.Any()); // join никому нельзя — пауза даже не запрашивалась (python L480–481) } [Fact] @@ -417,7 +420,7 @@ public sealed class DiscoveryWorkerServiceTests FakeDiscoveryStore Store, FakeSettingsStore Settings, FakeDiscoveryGateway Gateway, - FakeDiscoveryPacer Pacer, + IDiscoveryPacer Pacer, DiscoveryWorkerService Worker, DiscoveryTasksService Tasks); @@ -436,7 +439,7 @@ public sealed class DiscoveryWorkerServiceTests var planGuard = new DiscoveryPlanGuard(store, settings); var tasks = new DiscoveryTasksService(store, planGuard, settings); var banGuard = new DiscoveryBanGuard(store, settings); - var pacer = new FakeDiscoveryPacer(); + var pacer = Substitute.For(); var gateway = new FakeDiscoveryGateway(); var worker = new DiscoveryWorkerService( store, tasks, candidates, log, new DiscoveryEvaluator(settings, ml, ai), banGuard, pacer, gateway); diff --git a/src/core/tests/Deal.Tests.Unit/Modules/Discovery/FakeDiscoveryPacer.cs b/src/core/tests/Deal.Tests.Unit/Modules/Discovery/FakeDiscoveryPacer.cs deleted file mode 100644 index 286009d..0000000 --- a/src/core/tests/Deal.Tests.Unit/Modules/Discovery/FakeDiscoveryPacer.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Deal.Modules.Discovery.Application.Abstractions; - -namespace Deal.Tests.Unit.Modules.Discovery; - -/// -/// Фейковый для тестов воркера Discovery -/// -public sealed class FakeDiscoveryPacer : IDiscoveryPacer -{ - /// - /// Действие, выполняемое в момент паузы - /// - public Action? OnDelay { get; set; } - - /// - /// Сколько раз запрошена пауза перед авто-вступлением - /// - public int DelayCalls { get; private set; } - - /// - public Task WaitJoinDelayAsync(CancellationToken ct) - { - DelayCalls++; - OnDelay?.Invoke(); - return Task.CompletedTask; - } -}