Перевести FakeMlClient на NSubstitute

Хелпер Support/TestMlClient: Status/Predict сценарии через свойства, Pushed
журнал, PredictCalls через When/Do, ResetAsync бросает NotSupportedException.
Потребители (17 файлов) перетипизированы на .Client, фейк удалён,
тесты 1340 зелёные.
This commit is contained in:
2026-09-13 01:07:43 +03:00
parent c5fd516a36
commit 3ccbe4c9f5
18 changed files with 113 additions and 109 deletions
@@ -84,7 +84,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_ToBoard_WritesJournalAndPush()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_py", Rules(stack: new[] { "python" })));
store.SeedCard(Card("l_1", "inbox", sourceMsg: "Нужен Python-разработчик"));
@@ -105,7 +105,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_ToInbox_WritesJournalWithoutPush()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_py", Rules(stack: new[] { "python" })));
store.SeedCard(Card("l_1", "b_py", sourceMsg: "Нужен Python-разработчик", prevCol: "inbox"));
@@ -124,7 +124,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_ToUnknownBoard_Returns400Text()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedCard(Card("l_1", "inbox", sourceMsg: "Нужен Python-разработчик"));
CardResultDto result = await service.MoveDashboardCardAsync("l_1", "b_ghost", CancellationToken.None);
@@ -148,7 +148,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_CardMissing_ReturnsNullLead()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_py"));
CardResultDto result = await service.MoveDashboardCardAsync("l_ghost", "b_py", CancellationToken.None);
@@ -162,7 +162,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_SameColumn_NoOpWithoutJournalAndPush()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_py", Rules(stack: new[] { "python" })));
store.SeedCard(Card("l_1", "b_py", sourceMsg: "Нужен Python-разработчик", isNew: true));
@@ -179,7 +179,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_ToBoardWithoutRules_MatchHitsEmptyButPushSent()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_free")); // правил нет
store.SeedCard(Card("l_1", "inbox", sourceMsg: "Любой текст"));
@@ -195,7 +195,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_FromBoardToBoard_PrevColIsOldBoardAndPushNewLabel()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_java", Rules(stack: new[] { "java" })));
store.SeedBoard(Board("b_py", Rules(stack: new[] { "python" })));
store.SeedCard(Card("l_1", "b_java", sourceMsg: "Нужен Java-разработчик", prevCol: "inbox"));
@@ -214,7 +214,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_EmptySourceText_UsesTitleForMatchHitsAndPush()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_py", Rules(stack: new[] { "python" })));
store.SeedCard(Card("l_1", "inbox", title: "Middle Python-разработчик", sourceMsg: " "));
@@ -228,7 +228,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_EmptySourceAndTitle_JournalWithoutPush()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_py"));
store.SeedCard(Card("l_1", "inbox"));
@@ -263,7 +263,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_FromArchive_Returns400AndWritesNothing()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_py"));
store.SeedCard(Card("l_1", KanbanColumns.Archive, sourceMsg: "Старая вакансия", prevCol: "b_py"));
@@ -279,7 +279,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Move_FromTrash_Returns400AndWritesNothing()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_py"));
store.SeedCard(Card("l_1", KanbanColumns.Trash, sourceMsg: "Спам-текст", prevCol: "b_py"));
@@ -296,7 +296,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Trash_FromInbox_MovesToTrashWithJournalAndSpamPush()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedCard(Card("l_1", "inbox", sourceMsg: "Предлагаю услуги продвижения", isNew: true));
CardDto? result = await service.TrashCardAsync("l_1", CancellationToken.None);
@@ -318,7 +318,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Trash_AlreadyInTrash_NoOpWithoutJournalAndPush()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedCard(Card("l_1", KanbanColumns.Trash, sourceMsg: "Текст"));
CardDto? result = await service.TrashCardAsync("l_1", CancellationToken.None);
@@ -331,7 +331,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Trash_FromArchive_WritesJournalWithoutPush()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedCard(Card("l_1", KanbanColumns.Archive, sourceMsg: "Старая карточка", prevCol: "b_py"));
CardDto? result = await service.TrashCardAsync("l_1", CancellationToken.None);
@@ -346,7 +346,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Trash_CardMissing_ReturnsNull()
{
(CardsService service, _, _, FakeMlClient ml) = Create();
(CardsService service, _, _, TestMlClient ml) = Create();
CardDto? result = await service.TrashCardAsync("l_ghost", CancellationToken.None);
@@ -358,7 +358,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Restore_FromTrash_ToPrevColBoard_UnlearnsSpam()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_py", Rules(stack: new[] { "python" })));
store.SeedCard(Card("l_1", KanbanColumns.Trash, sourceMsg: "Нужен Python-разработчик", prevCol: "b_py"));
@@ -383,7 +383,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Restore_FromArchive_NoPush()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedBoard(Board("b_py"));
store.SeedCard(Card("l_1", KanbanColumns.Archive, sourceMsg: "Старая карточка", prevCol: "b_py"));
@@ -399,7 +399,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Restore_FromTrash_PrevColDeletedBoard_FallsBackToInbox()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedCard(Card("l_1", KanbanColumns.Trash, sourceMsg: "Текст", prevCol: "b_gone"));
string? back = await service.RestoreCardAsync("l_1", CancellationToken.None);
@@ -618,7 +618,7 @@ public sealed class CardsServiceTests
[Fact]
public async Task Counts_ColumnsNewAndMlStats()
{
(CardsService service, FakeKanjStore store, _, FakeMlClient ml) = Create();
(CardsService service, FakeKanjStore store, _, TestMlClient ml) = Create();
store.SeedCard(Card("l_1", "inbox", isNew: true));
store.SeedCard(Card("l_2", "inbox"));
store.SeedCard(Card("l_3", "b_py"));
@@ -711,12 +711,12 @@ public sealed class CardsServiceTests
// ─── Хелперы ──────────────────────────────────────────────────────────
private static (CardsService Service, FakeKanjStore Store, FakeSettingsStore Settings, FakeMlClient Ml) Create()
private static (CardsService Service, FakeKanjStore Store, FakeSettingsStore Settings, TestMlClient Ml) Create()
{
var store = new FakeKanjStore();
var settings = new FakeSettingsStore();
var ml = new FakeMlClient();
return (new CardsService(store, settings, ml, new TestFileStorage().Storage), store, settings, ml);
var ml = new TestMlClient();
return (new CardsService(store, settings, ml.Client, new TestFileStorage().Storage), store, settings, ml);
}
// Доска с правилами (ContainerRulesDto) либо без них.