diff --git a/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs b/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs index 42894a2..e37d653 100644 --- a/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs @@ -202,9 +202,7 @@ public static class CardDetailsEndpoints return EndpointResults.BadRequest(result.Error); } - return result.Card is null - ? EndpointResults.NotFound(CardNotFoundDetail) - : await ReadCardAsync(context, cardId, ct); + return await ReadCardAsync(context, cardId, ct); } // DELETE /api/cards/{cardId}/links/{linkId}: удалить ссылку. Ответ — карточка. @@ -220,10 +218,8 @@ public static class CardDetailsEndpoints } CardsService service = context.RequestServices.GetRequiredService(); - CardResultDto result = await service.RemoveLinkAsync(cardId, linkId, ct); - return result.Card is null - ? EndpointResults.NotFound(CardNotFoundDetail) - : await ReadCardAsync(context, cardId, ct); + await service.RemoveLinkAsync(cardId, linkId, ct); + return await ReadCardAsync(context, cardId, ct); } // POST /api/cards/{cardId}/files: загрузка вложений (multipart/form-data, поле files). @@ -240,10 +236,7 @@ public static class CardDetailsEndpoints } CardsService cardsService = context.RequestServices.GetRequiredService(); - if (await cardsService.GetCardAsync(cardId, ct) is null) - { - return EndpointResults.NotFound(CardNotFoundDetail); - } + await cardsService.GetCardAsync(cardId, ct); IFormCollection form; try @@ -357,9 +350,7 @@ public static class CardDetailsEndpoints return EndpointResults.BadRequest(result.Error); } - return result.Card is null - ? EndpointResults.NotFound(CardNotFoundDetail) - : await ReadCardAsync(context, cardId, ct); + return await ReadCardAsync(context, cardId, ct); } // DELETE /api/cards/{cardId}/reminder: снять напоминание. Ответ — карточка. @@ -374,9 +365,8 @@ public static class CardDetailsEndpoints } CardsService service = context.RequestServices.GetRequiredService(); - return await service.ClearReminderAsync(cardId, ct) - ? await ReadCardAsync(context, cardId, ct) - : EndpointResults.NotFound(CardNotFoundDetail); + await service.ClearReminderAsync(cardId, ct); + return await ReadCardAsync(context, cardId, ct); } // POST /api/cards/{cardId}/reminder/snooze: «напомнить позже» (now + 24 ч). Ответ — карточка. @@ -391,9 +381,8 @@ public static class CardDetailsEndpoints } CardsService service = context.RequestServices.GetRequiredService(); - return await service.SnoozeReminderAsync(cardId, ct) - ? await ReadCardAsync(context, cardId, ct) - : EndpointResults.NotFound(CardNotFoundDetail); + await service.SnoozeReminderAsync(cardId, ct); + return await ReadCardAsync(context, cardId, ct); } // Читает карточку через единый сервис и возвращает её как ответ (404 — карточки нет). @@ -412,11 +401,7 @@ public static class CardDetailsEndpoints CancellationToken ct) { CardsService cardsService = context.RequestServices.GetRequiredService(); - CardDto? card = await cardsService.GetCardAsync(cardId, ct); - if (card is null) - { - return EndpointResults.NotFound(CardNotFoundDetail); - } + CardDto card = await cardsService.GetCardAsync(cardId, ct); SourceContent content = await cardsService.ResolveSourceAsync(card, ct); return Results.Ok(content); @@ -428,10 +413,8 @@ public static class CardDetailsEndpoints CancellationToken ct) { CardsService cardsService = context.RequestServices.GetRequiredService(); - CardDto? card = await cardsService.GetCardAsync(cardId, ct); - return card is null - ? EndpointResults.NotFound(CardNotFoundDetail) - : Results.Ok(card); + CardDto card = await cardsService.GetCardAsync(cardId, ct); + return Results.Ok(card); } // Имя файла для Content-Disposition без кавычек «"». diff --git a/src/core/Deal.Api/Endpoints/CardsEndpoints.cs b/src/core/Deal.Api/Endpoints/CardsEndpoints.cs index a000360..68856da 100644 --- a/src/core/Deal.Api/Endpoints/CardsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/CardsEndpoints.cs @@ -128,10 +128,8 @@ public static class CardsEndpoints } CardsService cardsService = context.RequestServices.GetRequiredService(); - CardDto? card = await cardsService.GetCardAsync(cardId, ct); - return card is null - ? EndpointResults.NotFound(CardNotFoundDetail) - : Results.Ok(card); + CardDto card = await cardsService.GetCardAsync(cardId, ct); + return Results.Ok(card); } private static async Task MarkAllSeenAsync(HttpContext context, CancellationToken ct) @@ -184,18 +182,11 @@ public static class CardsEndpoints return EndpointResults.BadRequest(outcome.Error); } - if (!outcome.Exists) - { - return EndpointResults.NotFound(CardNotFoundDetail); - } - await AuditAppender.AppendTenantAsync(context, AuditEvents.CardMoved, new { cardId, to = body.To }, ct); CardsService cardsService = context.RequestServices.GetRequiredService(); - CardDto? unified = await cardsService.GetCardAsync(cardId, ct); - return unified is null - ? EndpointResults.NotFound(CardNotFoundDetail) - : Results.Ok(unified); + CardDto unified = await cardsService.GetCardAsync(cardId, ct); + return Results.Ok(unified); } private static async Task TrashAsync( @@ -353,11 +344,7 @@ public static class CardsEndpoints } CardsService cardsService = context.RequestServices.GetRequiredService(); - CardDto? card = await cardsService.GetCardAsync(cardId, ct); - if (card is null) - { - return EndpointResults.NotFound(CardNotFoundDetail); - } + CardDto card = await cardsService.GetCardAsync(cardId, ct); CardReclassifier reclassifier = context.RequestServices.GetRequiredService(); ReclassifyResultDto result = await reclassifier.ReclassifyCardAsync(card, ct); diff --git a/src/core/Deal.Infrastructure/Services/CardMover.cs b/src/core/Deal.Infrastructure/Services/CardMover.cs index 5739095..1f59b4c 100644 --- a/src/core/Deal.Infrastructure/Services/CardMover.cs +++ b/src/core/Deal.Infrastructure/Services/CardMover.cs @@ -22,8 +22,6 @@ public sealed class CardMover(CardsService cardsService) : ICardMover CardResultDto result = CardsDefaultContainers.Contains(toContainerId) ? await cardsService.MoveStageCardAsync(cardId, toContainerId, ct) : await cardsService.MoveDashboardCardAsync(cardId, toContainerId, ct); - return result.Error is not null - ? new CardMoveResultDto(result.Error, Exists: true) - : new CardMoveResultDto(null, Exists: result.Card is not null); + return new CardMoveResultDto(result.Error); } } diff --git a/src/core/Deal.Modules.Cards/Application/Abstractions/ICardMover.cs b/src/core/Deal.Modules.Cards/Application/Abstractions/ICardMover.cs index 904ae3f..21f1a26 100644 --- a/src/core/Deal.Modules.Cards/Application/Abstractions/ICardMover.cs +++ b/src/core/Deal.Modules.Cards/Application/Abstractions/ICardMover.cs @@ -1,5 +1,6 @@ using Deal.Modules.Cards.Application.Dtos; using Deal.Modules.Cards.Application.Models; +using Deal.SharedKernel.Errors; namespace Deal.Modules.Cards.Application.Abstractions; @@ -14,7 +15,8 @@ public interface ICardMover /// Id карточки. /// Id контейнера назначения (стадия «Выбранных» либо дашборд-контейнер). /// Контекст перехода (инициатор, причина, обучение). - /// Результат: Error (400-текст отказа) | Exists=false (карточки нет, 404) | успех (Exists=true). + /// Результат: Error (400-текст отказа) либо null (успех). + /// Карточка не найдена. public Task MoveAsync( string cardId, string toContainerId, diff --git a/src/core/Deal.Modules.Cards/Application/Dtos/CardMoveResultDto.cs b/src/core/Deal.Modules.Cards/Application/Dtos/CardMoveResultDto.cs index 166419e..06bfa6a 100644 --- a/src/core/Deal.Modules.Cards/Application/Dtos/CardMoveResultDto.cs +++ b/src/core/Deal.Modules.Cards/Application/Dtos/CardMoveResultDto.cs @@ -5,6 +5,5 @@ namespace Deal.Modules.Cards.Application.Dtos; /// /// Результат перехода карточки единым механизмом . /// -/// Текст 400-ошибки либо null. -/// True — карточка найдена и переход выполнен (либо перенос был no-op). -public sealed record CardMoveResultDto(string? Error, bool Exists); +/// Текст 400-ошибки либо null (успех). +public sealed record CardMoveResultDto(string? Error); diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs index 1378c99..d24c6a8 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs @@ -27,9 +27,16 @@ public sealed partial class CardsService /// /// Id карточки (c_...). /// Карточка или null — строки нет (эндпоинт отвечает 404 «Карточка не найдена»). - public Task GetCardAsync(string cardId, CancellationToken ct) + /// + /// Одна карточка по id. + /// + /// Id карточки (c_...). + /// Карточка. + /// Карточка не найдена. + public async Task GetCardAsync(string cardId, CancellationToken ct) { - return _store.GetCardAsync(cardId, ct); + return await _store.GetCardAsync(cardId, ct) + ?? throw new NotFoundException(CardEntityName, cardId); } @@ -57,7 +64,7 @@ public sealed partial class CardsService CardDto? card = await _store.GetCardAsync(cardId, ct); if (card is null) { - return new CardResultDto(null, null); + throw new NotFoundException(CardEntityName, cardId); } if (card.Col == CardIds.Archive || card.Col == CardIds.Trash) @@ -81,7 +88,10 @@ public sealed partial class CardsService await _mlClient.PushAsync(text, toCol, PushWeightUser, ct); } - return new CardResultDto(null, await _store.GetCardAsync(cardId, ct)); + return new CardResultDto( + null, + await _store.GetCardAsync(cardId, ct) + ?? throw new NotFoundException(CardEntityName, cardId)); } /// diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Reminders.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Reminders.cs index c1c6b56..f4214ff 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Reminders.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Reminders.cs @@ -1,5 +1,6 @@ using Deal.Modules.Kanban.Application.Models; using Deal.Modules.Settings.Application.Models; +using Deal.SharedKernel.Errors; namespace Deal.Modules.Kanban.Application.Services; @@ -22,17 +23,15 @@ public sealed partial class CardsService /// /// Id карточки (c_...). /// Время напоминания, epoch-ms. - /// Результат: Error (400) | Card=null без Error (404) | Card — карточка с напоминанием. + /// Результат: Error (400) | Card — карточка с напоминанием. + /// Карточка не найдена. public async Task SetReminderAsync( string cardId, long atMs, CancellationToken ct) { - CardDto? card = await _store.GetCardAsync(cardId, ct); - if (card is null) - { - return new CardResultDto(null, null); - } + CardDto card = await _store.GetCardAsync(cardId, ct) + ?? throw new NotFoundException(CardEntityName, cardId); if (!await ReadRemindersEnabledAsync(ct)) { @@ -41,7 +40,7 @@ public sealed partial class CardsService await _store.SetReminderAsync(cardId, atMs, ct); CardDto saved = await _store.GetCardAsync(cardId, ct) - ?? throw new InvalidOperationException("Карточка не прочиталась после установки напоминания: " + cardId); + ?? throw new NotFoundException(CardEntityName, cardId); return new CardResultDto(null, saved); } @@ -49,35 +48,27 @@ public sealed partial class CardsService /// Снимает напоминание карточки. /// /// Id карточки (c_...). - /// True — карточка есть и напоминание снято; false — карточки нет (404). - public async Task ClearReminderAsync(string cardId, CancellationToken ct) + /// Карточка не найдена. + public async Task ClearReminderAsync(string cardId, CancellationToken ct) { - CardDto? card = await _store.GetCardAsync(cardId, ct); - if (card is null) - { - return false; - } + _ = await _store.GetCardAsync(cardId, ct) + ?? throw new NotFoundException(CardEntityName, cardId); await _store.ClearReminderAsync(cardId, ct); - return true; } /// /// «Напомнить позже» /// /// Id карточки (c_...). - /// True — карточка есть и напоминание отложено; false — карточки нет (404). - public async Task SnoozeReminderAsync(string cardId, CancellationToken ct) + /// Карточка не найдена. + public async Task SnoozeReminderAsync(string cardId, CancellationToken ct) { - CardDto? card = await _store.GetCardAsync(cardId, ct); - if (card is null) - { - return false; - } + _ = await _store.GetCardAsync(cardId, ct) + ?? throw new NotFoundException(CardEntityName, cardId); long snoozedAtMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + ReminderSnoozeMs; await _store.SetReminderAsync(cardId, snoozedAtMs, ct); - return true; } /// diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Selected.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Selected.cs index 69899fd..34b97ab 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Selected.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Selected.cs @@ -12,6 +12,9 @@ namespace Deal.Modules.Kanban.Application.Services; /// public sealed partial class CardsService { + // Имя сущности для текста ошибки «не найдено». + private const string CardLinkEntityName = "Ссылка карточки"; + /// /// 400 перенос по стадии /// @@ -159,18 +162,16 @@ public sealed partial class CardsService /// Id карточки (c_...). /// Название ссылки; пустое после Trim → name = url. /// URL ссылки (без схемы — добавится https://). - /// Результат: Error (400 «Пустая ссылка») | Card=null без Error (404) | Card — карточка со ссылкой. + /// Результат: Error (400 «Пустая ссылка») | Card — карточка со ссылкой. + /// Карточка не найдена. public async Task AddLinkAsync( string cardId, string name, string url, CancellationToken ct) { - CardDto? card = await _store.GetCardAsync(cardId, ct); - if (card is null) - { - return new CardResultDto(null, null); - } + CardDto card = await _store.GetCardAsync(cardId, ct) + ?? throw new NotFoundException(CardEntityName, cardId); string normalizedUrl = (url ?? string.Empty).Trim(); if (normalizedUrl.Length == 0) @@ -191,11 +192,11 @@ public sealed partial class CardsService normalizedUrl); if (!await _store.AddLinkAsync(cardId, link, ct)) { - return new CardResultDto(null, null); + throw new NotFoundException(CardEntityName, cardId); } CardDto saved = await _store.GetCardAsync(cardId, ct) - ?? throw new InvalidOperationException("Карточка не прочиталась после добавления ссылки: " + cardId); + ?? throw new NotFoundException(CardEntityName, cardId); return new CardResultDto(null, saved); } @@ -204,7 +205,8 @@ public sealed partial class CardsService /// /// Id карточки (c_...). /// Id удаляемой ссылки (pl_...). - /// Результат: Card=null без Error (404) | Card — карточка без ссылки. + /// Карточка без ссылки. + /// Карточка или ссылка не найдены. public async Task RemoveLinkAsync( string cardId, string linkId, @@ -212,11 +214,11 @@ public sealed partial class CardsService { if (!await _store.RemoveLinkAsync(cardId, linkId, ct)) { - return new CardResultDto(null, null); + throw new NotFoundException(CardLinkEntityName, linkId); } CardDto saved = await _store.GetCardAsync(cardId, ct) - ?? throw new InvalidOperationException("Карточка не прочиталась после удаления ссылки: " + cardId); + ?? throw new NotFoundException(CardEntityName, cardId); return new CardResultDto(null, saved); } @@ -225,7 +227,8 @@ public sealed partial class CardsService /// /// Id карточки (c_...). /// Новый контейнер-стадия — id каталога . - /// Результат: Error «Неизвестная стадия» (400) | Card=null без Error (карточки нет, 404) | Card — карточка после переноса. + /// Результат: Error «Неизвестная стадия» (400) | Card — карточка после переноса. + /// Карточка не найдена. public async Task MoveStageCardAsync( string cardId, string containerId, @@ -241,11 +244,11 @@ public sealed partial class CardsService bool moved = await _store.MoveCardStageAsync(cardId, containerId, entry, nowMs, ct); if (!moved) { - return new CardResultDto(null, null); + throw new NotFoundException(CardEntityName, cardId); } CardDto card = await _store.GetCardAsync(cardId, ct) - ?? throw new InvalidOperationException("Карточка не прочиталась после move: " + cardId); + ?? throw new NotFoundException(CardEntityName, cardId); return new CardResultDto(null, card); } diff --git a/src/core/tests/Deal.Tests.Unit/Contracts/CardsServiceTests.cs b/src/core/tests/Deal.Tests.Unit/Contracts/CardsServiceTests.cs index d6ec709..f008aa6 100644 --- a/src/core/tests/Deal.Tests.Unit/Contracts/CardsServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/Contracts/CardsServiceTests.cs @@ -53,13 +53,12 @@ public sealed class CardsServiceTests } [Fact] - public async Task GetCard_Missing_ReturnsNull() + public async Task GetCard_Missing_ThrowsNotFound() { (CardsService service, _, _, _) = Create(); - CardDto? card = await service.GetCardAsync("l_missing", CancellationToken.None); - - Assert.Null(card); + await Assert.ThrowsAsync( + () => service.GetCardAsync("l_missing", CancellationToken.None)); } @@ -146,15 +145,14 @@ public sealed class CardsServiceTests } [Fact] - public async Task Move_CardMissing_ReturnsNullLead() + public async Task Move_CardMissing_ThrowsNotFound() { (CardsService service, TestKanjStore store, _, TestMlClient ml) = Create(); store.SeedBoard(Board("b_py")); - CardResultDto result = await service.MoveDashboardCardAsync("l_ghost", "b_py", CancellationToken.None); + await Assert.ThrowsAsync( + () => service.MoveDashboardCardAsync("l_ghost", "b_py", CancellationToken.None)); - Assert.Null(result.Error); - Assert.Null(result.Card); // эндпоинт отвечает 404 «Карточка не найдена» Assert.Empty(store.Moves); Assert.Empty(ml.Pushed); } diff --git a/src/core/tests/Deal.Tests.Unit/Infrastructure/CardMoverTests.cs b/src/core/tests/Deal.Tests.Unit/Infrastructure/CardMoverTests.cs index 8bcd7bd..b33382b 100644 --- a/src/core/tests/Deal.Tests.Unit/Infrastructure/CardMoverTests.cs +++ b/src/core/tests/Deal.Tests.Unit/Infrastructure/CardMoverTests.cs @@ -3,10 +3,11 @@ using Deal.Modules.Cards.Application.Dtos; using Deal.Modules.Cards.Application.Models; using Deal.Modules.Cards.Application.Sources; using Deal.Modules.Kanban.Application.Models; -using Deal.Tests.Unit.Support; using Deal.Modules.Kanban.Application.Services; +using Deal.SharedKernel.Errors; using Deal.Tests.Unit.Contracts; using Deal.Tests.Unit.Modules.Settings; +using Deal.Tests.Unit.Support; namespace Deal.Tests.Unit.Infrastructure; @@ -32,7 +33,6 @@ public sealed class CardMoverTests CardMoveResultDto result = await mover.MoveAsync("c_1", CardsDefaultContainers.Planned, UserMove, CancellationToken.None); Assert.Null(result.Error); - Assert.True(result.Exists); CardDto card = Assert.Single(store.CardDtos); Assert.Equal(CardsDefaultContainers.Planned, card.Col); Assert.Null(card.Reminder); // move по стадии сбрасывает напоминание @@ -50,7 +50,6 @@ public sealed class CardMoverTests CardMoveResultDto result = await mover.MoveAsync("c_1", "b_py", UserMove, CancellationToken.None); Assert.Null(result.Error); - Assert.True(result.Exists); Assert.Equal("b_py", Assert.Single(store.CardDtos).Col); Assert.Single(store.Moves); // журнал CardMoves дашборд-переноса } @@ -64,18 +63,15 @@ public sealed class CardMoverTests CardMoveResultDto result = await mover.MoveAsync("c_1", "b_ghost", UserMove, CancellationToken.None); Assert.Equal(CardsService.MoveTargetInvalidDetail, result.Error); - Assert.True(result.Exists); // ошибка важнее признака наличия } [Fact] - public async Task Move_ToStage_CardMissing_ReportsNotFound() + public async Task Move_ToStage_CardMissing_ThrowsNotFound() { (CardMover mover, _) = Create(); - CardMoveResultDto result = await mover.MoveAsync("c_ghost", CardsDefaultContainers.Planned, UserMove, CancellationToken.None); - - Assert.Null(result.Error); - Assert.False(result.Exists); + await Assert.ThrowsAsync( + () => mover.MoveAsync("c_ghost", CardsDefaultContainers.Planned, UserMove, CancellationToken.None)); } private static (CardMover Mover, TestKanjStore Store) Create() diff --git a/src/core/tests/Deal.Tests.Unit/Modules/Kanban/CardsServiceRemindersTests.cs b/src/core/tests/Deal.Tests.Unit/Modules/Kanban/CardsServiceRemindersTests.cs index ea7bd08..b6974a8 100644 --- a/src/core/tests/Deal.Tests.Unit/Modules/Kanban/CardsServiceRemindersTests.cs +++ b/src/core/tests/Deal.Tests.Unit/Modules/Kanban/CardsServiceRemindersTests.cs @@ -1,9 +1,10 @@ using Deal.Modules.Kanban.Application.Models; -using Deal.Tests.Unit.Support; using Deal.Modules.Kanban.Application.Services; using Deal.Modules.Settings.Application.Models; +using Deal.SharedKernel.Errors; using Deal.Tests.Unit.Contracts; using Deal.Tests.Unit.Modules.Settings; +using Deal.Tests.Unit.Support; namespace Deal.Tests.Unit.Modules.Kanban; @@ -47,14 +48,12 @@ public sealed class CardsServiceRemindersTests } [Fact] - public async Task Set_MissingCard_Returns404EvenWhenDisabled() + public async Task Set_MissingCard_ThrowsNotFoundEvenWhenDisabled() { (CardsService service, _, _) = Create(remindersEnabled: false); - CardResultDto result = await service.SetReminderAsync("c_missing", NowMs(), CancellationToken.None); - - Assert.Null(result.Error); - Assert.Null(result.Card); // эндпоинт отвечает 404 «Карточка не найдена» + await Assert.ThrowsAsync( + () => service.SetReminderAsync("c_missing", NowMs(), CancellationToken.None)); } [Fact] @@ -73,25 +72,23 @@ public sealed class CardsServiceRemindersTests [Fact] - public async Task Clear_WithReminder_ClearsItAndReturnsTrue() + public async Task Clear_WithReminder_ClearsIt() { (CardsService service, TestKanjStore store, _) = Create(); store.SeedCard(Card("c_1", stage: "hold") with { Reminder = new CardReminderDto(NowMs() - 1) }); - bool cleared = await service.ClearReminderAsync("c_1", CancellationToken.None); + await service.ClearReminderAsync("c_1", CancellationToken.None); - Assert.True(cleared); Assert.Null(Assert.Single(store.CardDtos).Reminder); // reminder_at=NULL, fired сброшен } [Fact] - public async Task Clear_MissingCard_ReturnsFalse() + public async Task Clear_MissingCard_ThrowsNotFound() { (CardsService service, _, _) = Create(); - bool cleared = await service.ClearReminderAsync("c_missing", CancellationToken.None); - - Assert.False(cleared); // эндпоинт отвечает 404 «Карточка не найдена» + await Assert.ThrowsAsync( + () => service.ClearReminderAsync("c_missing", CancellationToken.None)); } [Fact] @@ -100,9 +97,8 @@ public sealed class CardsServiceRemindersTests (CardsService service, TestKanjStore store, _) = Create(remindersEnabled: false); store.SeedCard(Card("c_1", stage: "hold") with { Reminder = new CardReminderDto(NowMs() + DayMs) }); - bool cleared = await service.ClearReminderAsync("c_1", CancellationToken.None); + await service.ClearReminderAsync("c_1", CancellationToken.None); - Assert.True(cleared); Assert.Null(Assert.Single(store.CardDtos).Reminder); } @@ -114,22 +110,20 @@ public sealed class CardsServiceRemindersTests store.SeedCard(Card("c_1", stage: "hold") with { Reminder = new CardReminderDto(NowMs() - 1) }); long beforeMs = NowMs(); - bool snoozed = await service.SnoozeReminderAsync("c_1", CancellationToken.None); + await service.SnoozeReminderAsync("c_1", CancellationToken.None); - Assert.True(snoozed); long afterMs = NowMs(); CardReminderDto reminder = Assert.Single(store.CardDtos).Reminder!; Assert.InRange(reminder.At, beforeMs + DayMs, afterMs + DayMs); // now + 24 ч } [Fact] - public async Task Snooze_MissingCard_ReturnsFalse() + public async Task Snooze_MissingCard_ThrowsNotFound() { (CardsService service, _, _) = Create(); - bool snoozed = await service.SnoozeReminderAsync("c_missing", CancellationToken.None); - - Assert.False(snoozed); // эндпоинт отвечает 404 «Карточка не найдена» + await Assert.ThrowsAsync( + () => service.SnoozeReminderAsync("c_missing", CancellationToken.None)); } [Fact] @@ -138,9 +132,8 @@ public sealed class CardsServiceRemindersTests (CardsService service, TestKanjStore store, _) = Create(remindersEnabled: false); store.SeedCard(Card("c_1", stage: "hold") with { Reminder = new CardReminderDto(NowMs() - 1) }); - bool snoozed = await service.SnoozeReminderAsync("c_1", CancellationToken.None); + await service.SnoozeReminderAsync("c_1", CancellationToken.None); - Assert.True(snoozed); Assert.NotNull(Assert.Single(store.CardDtos).Reminder); } diff --git a/src/core/tests/Deal.Tests.Unit/Support/CardsServiceSelectedTests.cs b/src/core/tests/Deal.Tests.Unit/Support/CardsServiceSelectedTests.cs index 871d678..21fa9a5 100644 --- a/src/core/tests/Deal.Tests.Unit/Support/CardsServiceSelectedTests.cs +++ b/src/core/tests/Deal.Tests.Unit/Support/CardsServiceSelectedTests.cs @@ -40,13 +40,12 @@ public sealed class CardsServiceSelectedTests } [Fact] - public async Task Get_Missing_ReturnsNull() + public async Task Get_Missing_ThrowsNotFound() { (CardsService service, _, _, _) = Create(); - CardDto? card = await service.GetCardAsync("c_missing", CancellationToken.None); - - Assert.Null(card); + await Assert.ThrowsAsync( + () => service.GetCardAsync("c_missing", CancellationToken.None)); } @@ -319,14 +318,12 @@ public sealed class CardsServiceSelectedTests } [Fact] - public async Task Move_CardMissing_ReturnsNullCard() + public async Task Move_CardMissing_ThrowsNotFound() { (CardsService service, _, _, _) = Create(); - CardResultDto result = await service.MoveStageCardAsync("c_missing", "work", CancellationToken.None); - - Assert.Null(result.Error); - Assert.Null(result.Card); // 404 «Карточка не найдена» — текст у эндпоинта + await Assert.ThrowsAsync( + () => service.MoveStageCardAsync("c_missing", "work", CancellationToken.None)); } @@ -465,14 +462,13 @@ public sealed class CardsServiceSelectedTests } [Fact] - public async Task AddLink_CardMissing_ReturnsNullCardBeforeUrlValidation() + public async Task AddLink_CardMissing_ThrowsBeforeUrlValidation() { (CardsService service, TestKanjStore store, _, _) = Create(); - CardResultDto result = await service.AddLinkAsync("c_missing", string.Empty, "example.com", CancellationToken.None); + await Assert.ThrowsAsync( + () => service.AddLinkAsync("c_missing", string.Empty, "example.com", CancellationToken.None)); - Assert.Null(result.Error); - Assert.Null(result.Card); // 404-семантика: карточки нет раньше валидации url Assert.Empty(store.CardDtos); } @@ -514,14 +510,13 @@ public sealed class CardsServiceSelectedTests } [Fact] - public async Task RemoveLink_CardMissing_ReturnsNullCard() + public async Task RemoveLink_CardMissing_ThrowsNotFound() { (CardsService service, TestKanjStore store, _, _) = Create(); - CardResultDto result = await service.RemoveLinkAsync("c_missing", "pl_1", CancellationToken.None); + await Assert.ThrowsAsync( + () => service.RemoveLinkAsync("c_missing", "pl_1", CancellationToken.None)); - Assert.Null(result.Error); - Assert.Null(result.Card); // эндпоинт отвечает 404 «Карточка не найдена» Assert.Empty(store.CardDtos); }