DiscoveryTasksService (get/patch/delete/start/pause) бросает NotFoundException вместо null; эндпоинты отдают 404 через общий обработчик.
This commit is contained in:
@@ -55,8 +55,7 @@ public static class DiscoveryEndpoints
|
||||
// Путь лога задачи (GET).
|
||||
private const string TaskLogPath = "/tasks/{task_id}/log";
|
||||
|
||||
private const string TaskNotFoundDetail = "Задача не найдена";
|
||||
|
||||
// 404: кандидат не найден.
|
||||
private const string CandidateNotFoundDetail = "Кандидат не найден";
|
||||
|
||||
private const string AlreadyJoinedDetail = "Уже вступили в этот источник";
|
||||
@@ -149,8 +148,8 @@ public static class DiscoveryEndpoints
|
||||
try
|
||||
{
|
||||
DiscoveryTasksService tasks = context.RequestServices.GetRequiredService<DiscoveryTasksService>();
|
||||
DiscoveryTaskDto? task = await tasks.PatchAsync(task_id, ToPatch(body), ct);
|
||||
return task is null ? EndpointResults.NotFound(TaskNotFoundDetail) : Results.Ok(task);
|
||||
DiscoveryTaskDto task = await tasks.PatchAsync(task_id, ToPatch(body), ct);
|
||||
return Results.Ok(task);
|
||||
}
|
||||
catch (DiscoveryValidationException exception)
|
||||
{
|
||||
@@ -169,8 +168,8 @@ public static class DiscoveryEndpoints
|
||||
}
|
||||
|
||||
DiscoveryTasksService tasks = context.RequestServices.GetRequiredService<DiscoveryTasksService>();
|
||||
bool deleted = await tasks.DeleteAsync(task_id, ct);
|
||||
return deleted ? Results.Ok(new { ok = true }) : EndpointResults.NotFound(TaskNotFoundDetail);
|
||||
await tasks.DeleteAsync(task_id, ct);
|
||||
return Results.Ok(new { ok = true });
|
||||
}
|
||||
|
||||
private static async Task<IResult> StartTaskAsync(
|
||||
@@ -186,8 +185,8 @@ public static class DiscoveryEndpoints
|
||||
try
|
||||
{
|
||||
DiscoveryTasksService tasks = context.RequestServices.GetRequiredService<DiscoveryTasksService>();
|
||||
DiscoveryTaskDto? task = await tasks.StartAsync(task_id, ct);
|
||||
return task is null ? EndpointResults.NotFound(TaskNotFoundDetail) : Results.Ok(task);
|
||||
DiscoveryTaskDto task = await tasks.StartAsync(task_id, ct);
|
||||
return Results.Ok(task);
|
||||
}
|
||||
catch (DiscoveryValidationException exception)
|
||||
{
|
||||
@@ -206,8 +205,8 @@ public static class DiscoveryEndpoints
|
||||
}
|
||||
|
||||
DiscoveryTasksService tasks = context.RequestServices.GetRequiredService<DiscoveryTasksService>();
|
||||
DiscoveryTaskDto? task = await tasks.PauseAsync(task_id, ct);
|
||||
return task is null ? EndpointResults.NotFound(TaskNotFoundDetail) : Results.Ok(task);
|
||||
DiscoveryTaskDto task = await tasks.PauseAsync(task_id, ct);
|
||||
return Results.Ok(task);
|
||||
}
|
||||
|
||||
private static async Task<IResult> GenerateKeywordsAsync(
|
||||
@@ -221,11 +220,7 @@ public static class DiscoveryEndpoints
|
||||
}
|
||||
|
||||
DiscoveryTasksService tasks = context.RequestServices.GetRequiredService<DiscoveryTasksService>();
|
||||
DiscoveryTaskDto? task = await tasks.GetAsync(task_id, ct);
|
||||
if (task is null)
|
||||
{
|
||||
return EndpointResults.NotFound(TaskNotFoundDetail);
|
||||
}
|
||||
DiscoveryTaskDto task = await tasks.GetAsync(task_id, ct);
|
||||
|
||||
ISettingsStore settings = context.RequestServices.GetRequiredService<ISettingsStore>();
|
||||
if (!await ReadAiEnabledAsync(settings, ct))
|
||||
@@ -268,11 +263,7 @@ public static class DiscoveryEndpoints
|
||||
}
|
||||
|
||||
DiscoveryTasksService tasks = context.RequestServices.GetRequiredService<DiscoveryTasksService>();
|
||||
DiscoveryTaskDto? task = await tasks.GetAsync(task_id, ct);
|
||||
if (task is null)
|
||||
{
|
||||
return EndpointResults.NotFound(TaskNotFoundDetail);
|
||||
}
|
||||
DiscoveryTaskDto task = await tasks.GetAsync(task_id, ct);
|
||||
|
||||
DiscoveryCandidatesService candidates = context.RequestServices.GetRequiredService<DiscoveryCandidatesService>();
|
||||
IReadOnlyList<DiscoveryCandidateDto> items = await candidates.ListAsync(task_id, status, ct);
|
||||
@@ -403,11 +394,7 @@ public static class DiscoveryEndpoints
|
||||
}
|
||||
|
||||
DiscoveryTasksService tasks = context.RequestServices.GetRequiredService<DiscoveryTasksService>();
|
||||
DiscoveryTaskDto? task = await tasks.GetAsync(task_id, ct);
|
||||
if (task is null)
|
||||
{
|
||||
return EndpointResults.NotFound(TaskNotFoundDetail);
|
||||
}
|
||||
DiscoveryTaskDto task = await tasks.GetAsync(task_id, ct);
|
||||
|
||||
DiscoveryLogService log = context.RequestServices.GetRequiredService<DiscoveryLogService>();
|
||||
IReadOnlyList<DiscoveryLogDto> items = await log.TaskLogAsync(task_id, ct);
|
||||
|
||||
@@ -4,6 +4,7 @@ using Deal.Modules.Discovery.Application.Extensions;
|
||||
using Deal.Modules.Discovery.Application.Models;
|
||||
using Deal.Modules.Settings.Application.Abstractions;
|
||||
using Deal.Modules.Settings.Application.Models;
|
||||
using Deal.SharedKernel.Errors;
|
||||
|
||||
namespace Deal.Modules.Discovery.Application.Services;
|
||||
|
||||
@@ -12,6 +13,9 @@ namespace Deal.Modules.Discovery.Application.Services;
|
||||
/// </summary>
|
||||
public sealed class DiscoveryTasksService(IDiscoveryStore store, DiscoveryPlanGuard planGuard, ISettingsStore settings)
|
||||
{
|
||||
// Имя сущности для текста ошибки «не найдено».
|
||||
private const string TaskEntityName = "Задача поиска";
|
||||
|
||||
/// <summary>
|
||||
/// 400 create: пустое название после Trim.
|
||||
/// </summary>
|
||||
@@ -35,10 +39,12 @@ public sealed class DiscoveryTasksService(IDiscoveryStore store, DiscoveryPlanGu
|
||||
/// Одна задача по id.
|
||||
/// </summary>
|
||||
/// <param name="taskId">Id задачи (<c>dt_...</c>).</param>
|
||||
/// <returns>Задача или null (404 «Задача не найдена» у эндпоинта).</returns>
|
||||
public Task<DiscoveryTaskDto?> GetAsync(string taskId, CancellationToken ct)
|
||||
/// <returns>Задача.</returns>
|
||||
/// <exception cref="NotFoundException">Задача не найдена.</exception>
|
||||
public async Task<DiscoveryTaskDto> GetAsync(string taskId, CancellationToken ct)
|
||||
{
|
||||
return store.GetTaskAsync(taskId, ct);
|
||||
return await store.GetTaskAsync(taskId, ct).ConfigureAwait(false)
|
||||
?? throw new NotFoundException(TaskEntityName, taskId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -102,18 +108,16 @@ public sealed class DiscoveryTasksService(IDiscoveryStore store, DiscoveryPlanGu
|
||||
/// </summary>
|
||||
/// <param name="taskId">Id задачи (<c>dt_...</c>).</param>
|
||||
/// <param name="patch">Изменяемые поля (null — не меняется).</param>
|
||||
/// <returns>Обновлённая задача либо null (404 «Задача не найдена»).</returns>
|
||||
/// <returns>Обновлённая задача.</returns>
|
||||
/// <exception cref="NotFoundException">Задача не найдена.</exception>
|
||||
/// <exception cref="DiscoveryValidationException">Новый план вне границ / бюджет исчерпан.</exception>
|
||||
public async Task<DiscoveryTaskDto?> PatchAsync(
|
||||
public async Task<DiscoveryTaskDto> PatchAsync(
|
||||
string taskId,
|
||||
DiscoveryTaskPatch patch,
|
||||
CancellationToken ct)
|
||||
{
|
||||
DiscoveryTaskDto? current = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false);
|
||||
if (current is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
DiscoveryTaskDto current = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false)
|
||||
?? throw new NotFoundException(TaskEntityName, taskId);
|
||||
|
||||
DiscoveryTaskPatch normalized = NormalizeTaskPatch(patch);
|
||||
if (normalized.PlanJoins is int newPlan)
|
||||
@@ -131,38 +135,34 @@ public sealed class DiscoveryTasksService(IDiscoveryStore store, DiscoveryPlanGu
|
||||
}
|
||||
|
||||
await store.PatchTaskAsync(taskId, normalized, ct).ConfigureAwait(false);
|
||||
return await store.GetTaskAsync(taskId, ct).ConfigureAwait(false);
|
||||
return await store.GetTaskAsync(taskId, ct).ConfigureAwait(false)
|
||||
?? throw new NotFoundException(TaskEntityName, taskId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет задачу вместе с кандидатами и логом.
|
||||
/// </summary>
|
||||
/// <param name="taskId">Id задачи (<c>dt_...</c>).</param>
|
||||
/// <returns>True — задача удалена; false — строки нет (404 у эндпоинта).</returns>
|
||||
public async Task<bool> DeleteAsync(string taskId, CancellationToken ct)
|
||||
/// <exception cref="NotFoundException">Задача не найдена.</exception>
|
||||
public async Task DeleteAsync(string taskId, CancellationToken ct)
|
||||
{
|
||||
DiscoveryTaskDto? current = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false);
|
||||
if (current is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
DiscoveryTaskDto current = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false)
|
||||
?? throw new NotFoundException(TaskEntityName, taskId);
|
||||
|
||||
return await store.DeleteTaskAsync(taskId, ct).ConfigureAwait(false);
|
||||
await store.DeleteTaskAsync(current.Id, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запускает поиск
|
||||
/// </summary>
|
||||
/// <param name="taskId">Id задачи (<c>dt_...</c>).</param>
|
||||
/// <returns>Задача в running либо null (404).</returns>
|
||||
/// <returns>Задача в running.</returns>
|
||||
/// <exception cref="NotFoundException">Задача не найдена.</exception>
|
||||
/// <exception cref="DiscoveryValidationException">Ключевых слов нет.</exception>
|
||||
public async Task<DiscoveryTaskDto?> StartAsync(string taskId, CancellationToken ct)
|
||||
public async Task<DiscoveryTaskDto> StartAsync(string taskId, CancellationToken ct)
|
||||
{
|
||||
DiscoveryTaskDto? current = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false);
|
||||
if (current is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
DiscoveryTaskDto current = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false)
|
||||
?? throw new NotFoundException(TaskEntityName, taskId);
|
||||
|
||||
if (current.Keywords.Count == 0)
|
||||
{
|
||||
@@ -171,24 +171,24 @@ public sealed class DiscoveryTasksService(IDiscoveryStore store, DiscoveryPlanGu
|
||||
|
||||
bool resetProgress = DiscoveryTaskStatuses.IsFinished(current.Status);
|
||||
await store.SetTaskRunningAsync(taskId, resetProgress, ct).ConfigureAwait(false);
|
||||
return await store.GetTaskAsync(taskId, ct).ConfigureAwait(false);
|
||||
return await store.GetTaskAsync(taskId, ct).ConfigureAwait(false)
|
||||
?? throw new NotFoundException(TaskEntityName, taskId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ставит задачу на паузу.
|
||||
/// </summary>
|
||||
/// <param name="taskId">Id задачи (<c>dt_...</c>).</param>
|
||||
/// <returns>Задача в paused либо null (404).</returns>
|
||||
public async Task<DiscoveryTaskDto?> PauseAsync(string taskId, CancellationToken ct)
|
||||
/// <returns>Задача в paused.</returns>
|
||||
/// <exception cref="NotFoundException">Задача не найдена.</exception>
|
||||
public async Task<DiscoveryTaskDto> PauseAsync(string taskId, CancellationToken ct)
|
||||
{
|
||||
DiscoveryTaskDto? current = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false);
|
||||
if (current is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
DiscoveryTaskDto current = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false)
|
||||
?? throw new NotFoundException(TaskEntityName, taskId);
|
||||
|
||||
await store.SetTaskPausedAsync(taskId, ct).ConfigureAwait(false);
|
||||
return await store.GetTaskAsync(taskId, ct).ConfigureAwait(false);
|
||||
await store.SetTaskPausedAsync(current.Id, ct).ConfigureAwait(false);
|
||||
return await store.GetTaskAsync(taskId, ct).ConfigureAwait(false)
|
||||
?? throw new NotFoundException(TaskEntityName, taskId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Deal.Modules.Discovery.Application.Exceptions;
|
||||
using Deal.Modules.Discovery.Application.Models;
|
||||
using Deal.Tests.Unit.Support;
|
||||
using Deal.Modules.Discovery.Application.Services;
|
||||
using Deal.SharedKernel.Errors;
|
||||
using Deal.Tests.Unit.Modules.Settings;
|
||||
using Deal.Tests.Unit.Support;
|
||||
|
||||
namespace Deal.Tests.Unit.Modules.Discovery;
|
||||
|
||||
@@ -131,14 +132,13 @@ public sealed class DiscoveryTasksServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_MissingTask_ReturnsNull()
|
||||
public async Task Patch_MissingTask_ThrowsNotFound()
|
||||
{
|
||||
(DiscoveryTasksService service, _, _) = Create();
|
||||
|
||||
DiscoveryTaskDto? patched = await service.PatchAsync(
|
||||
"dt_missing", new DiscoveryTaskPatch { Name = "Новое" }, CancellationToken.None);
|
||||
|
||||
Assert.Null(patched);
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.PatchAsync(
|
||||
"dt_missing", new DiscoveryTaskPatch { Name = "Новое" }, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -201,13 +201,12 @@ public sealed class DiscoveryTasksServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Start_MissingTask_ReturnsNull()
|
||||
public async Task Start_MissingTask_ThrowsNotFound()
|
||||
{
|
||||
(DiscoveryTasksService service, _, _) = Create();
|
||||
|
||||
DiscoveryTaskDto? task = await service.StartAsync("dt_missing", CancellationToken.None);
|
||||
|
||||
Assert.Null(task);
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.StartAsync("dt_missing", CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -281,9 +280,8 @@ public sealed class DiscoveryTasksServiceTests
|
||||
store.SeedCandidate(Candidate("c_2", "dt_2"));
|
||||
await store.Store.UpsertBlacklistAsync("c_1", "Источник", "причина", CancellationToken.None);
|
||||
|
||||
bool deleted = await service.DeleteAsync("dt_1", CancellationToken.None);
|
||||
await service.DeleteAsync("dt_1", CancellationToken.None);
|
||||
|
||||
Assert.True(deleted);
|
||||
Assert.Single(store.Tasks); // dt_2 осталась
|
||||
Assert.Equal("dt_2", Assert.Single(store.Tasks).Id);
|
||||
Assert.Single(store.Candidates); // кандидат dt_2 остался
|
||||
@@ -292,13 +290,12 @@ public sealed class DiscoveryTasksServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Delete_MissingTask_ReturnsFalse()
|
||||
public async Task Delete_MissingTask_ThrowsNotFound()
|
||||
{
|
||||
(DiscoveryTasksService service, _, _) = Create();
|
||||
|
||||
bool deleted = await service.DeleteAsync("dt_missing", CancellationToken.None);
|
||||
|
||||
Assert.False(deleted);
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => service.DeleteAsync("dt_missing", CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user