From 988507879175812648091d2660e8b87f8668fdde Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 4 Mar 2026 10:03:21 -0800 Subject: [PATCH 1/2] fix(handlers): propagate Result from repositories to endpoints (closes #81, #83, #85, #87) - Changed all Get/Update/Delete handlers to return Task> - Handlers now propagate repository Results instead of unwrapping - Endpoints map Result to HTTP responses (Ok/NotFound/Conflict/BadRequest) - Delete handlers return Result wrapping repository Result - Validation errors return Result.Fail with ResultErrorCode.Validation - NotFound errors use ResultErrorCode.NotFound - Conflict errors use ResultErrorCode.Conflict BREAKING: Handler return types changed - tests need update by Gimli Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- IssueManager.sln.DotSettings | 2 ++ src/Api/Handlers/Categories/CategoryEndpoints.cs | 10 ++++++---- .../Handlers/Categories/DeleteCategoryHandler.cs | 12 ++++++------ src/Api/Handlers/Categories/GetCategoryHandler.cs | 7 +++---- .../Handlers/Categories/UpdateCategoryHandler.cs | 14 +++++--------- src/Api/Handlers/Comments/CommentEndpoints.cs | 10 ++++++---- src/Api/Handlers/Comments/DeleteCommentHandler.cs | 12 ++++++------ src/Api/Handlers/Comments/GetCommentHandler.cs | 7 +++---- src/Api/Handlers/Comments/UpdateCommentHandler.cs | 14 +++++--------- src/Api/Handlers/Issues/DeleteIssueHandler.cs | 10 +++++----- src/Api/Handlers/Issues/GetIssueHandler.cs | 5 ++--- src/Api/Handlers/Issues/IssueEndpoints.cs | 12 ++++++++---- src/Api/Handlers/Issues/UpdateIssueHandler.cs | 14 +++++--------- .../Handlers/Issues/UpdateIssueStatusHandler.cs | 9 ++++----- src/Api/Handlers/Statuses/DeleteStatusHandler.cs | 12 ++++++------ src/Api/Handlers/Statuses/GetStatusHandler.cs | 7 +++---- src/Api/Handlers/Statuses/StatusEndpoints.cs | 10 ++++++---- src/Api/Handlers/Statuses/UpdateStatusHandler.cs | 14 +++++--------- 18 files changed, 86 insertions(+), 95 deletions(-) create mode 100644 IssueManager.sln.DotSettings diff --git a/IssueManager.sln.DotSettings b/IssueManager.sln.DotSettings new file mode 100644 index 0000000..fb7606e --- /dev/null +++ b/IssueManager.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/Api/Handlers/Categories/CategoryEndpoints.cs b/src/Api/Handlers/Categories/CategoryEndpoints.cs index d910984..f8ea7dd 100644 --- a/src/Api/Handlers/Categories/CategoryEndpoints.cs +++ b/src/Api/Handlers/Categories/CategoryEndpoints.cs @@ -25,8 +25,8 @@ public static IEndpointRouteBuilder MapCategoryEndpoints(this IEndpointRouteBuil if (!ObjectId.TryParse(id, out var objectId)) return Results.BadRequest("Invalid ID format"); var query = new GetCategoryQuery(objectId); - var category = await handler.Handle(query); - return category is not null ? Results.Ok(category) : Results.NotFound(); + var result = await handler.Handle(query); + return result.Success ? Results.Ok(result.Value) : Results.NotFound(); }) .WithName("GetCategory") .WithSummary("Get a category by ID") @@ -50,7 +50,9 @@ public static IEndpointRouteBuilder MapCategoryEndpoints(this IEndpointRouteBuil return Results.BadRequest("Invalid ID format"); var commandWithId = command with { Id = objectId }; var result = await handler.Handle(commandWithId); - return result is not null ? Results.Ok(result) : Results.NotFound(); + if (!result.Success) + return result.ErrorCode == ResultErrorCode.NotFound ? Results.NotFound() : Results.BadRequest(result.Error); + return Results.Ok(result.Value); }) .WithName("UpdateCategory") .WithSummary("Update an existing category") @@ -65,7 +67,7 @@ public static IEndpointRouteBuilder MapCategoryEndpoints(this IEndpointRouteBuil return Results.BadRequest("Invalid ID format"); var command = new DeleteCategoryCommand { Id = objectId }; var result = await handler.Handle(command); - return result ? Results.NoContent() : Results.NotFound(); + return result.Success ? Results.NoContent() : Results.NotFound(); }) .WithName("DeleteCategory") .WithSummary("Delete (archive) a category") diff --git a/src/Api/Handlers/Categories/DeleteCategoryHandler.cs b/src/Api/Handlers/Categories/DeleteCategoryHandler.cs index 768dc5c..60e445a 100644 --- a/src/Api/Handlers/Categories/DeleteCategoryHandler.cs +++ b/src/Api/Handlers/Categories/DeleteCategoryHandler.cs @@ -40,23 +40,23 @@ public DeleteCategoryHandler(ICategoryRepository repository, DeleteCategoryValid /// /// The command containing the category ID to delete. /// A token to cancel the asynchronous operation. - /// if the category was successfully archived; otherwise, . + /// A task that represents the asynchronous operation. The task result contains a result indicating success or failure. /// Thrown when the command fails validation. /// Thrown when the category is not found. - public async Task Handle(DeleteCategoryCommand command, CancellationToken cancellationToken = default) + public async Task> Handle(DeleteCategoryCommand command, CancellationToken cancellationToken = default) { var validationResult = await _validator.ValidateAsync(command, cancellationToken); if (!validationResult.IsValid) - throw new ValidationException(validationResult.Errors); + return Result.Fail("Validation failed", ResultErrorCode.Validation); var getResult = await _repository.GetByIdAsync(command.Id, cancellationToken); if (getResult.Failure || getResult.Value is null) - throw new NotFoundException($"Category with ID '{command.Id}' was not found."); + return Result.Fail($"Category with ID '{command.Id}' was not found.", ResultErrorCode.NotFound); if (getResult.Value.Archived) - return true; + return Result.Ok(true); var archiveResult = await _repository.ArchiveAsync(command.Id, cancellationToken); - return archiveResult.Success; + return archiveResult.Success ? Result.Ok(true) : Result.Fail(archiveResult.Error!, archiveResult.ErrorCode); } } diff --git a/src/Api/Handlers/Categories/GetCategoryHandler.cs b/src/Api/Handlers/Categories/GetCategoryHandler.cs index 3c6b181..1e4e0ee 100644 --- a/src/Api/Handlers/Categories/GetCategoryHandler.cs +++ b/src/Api/Handlers/Categories/GetCategoryHandler.cs @@ -39,11 +39,10 @@ public GetCategoryHandler(ICategoryRepository repository) /// /// The query containing the category ID to retrieve. /// A token to cancel the asynchronous operation. - /// A task that represents the asynchronous operation. The task result contains the category as a , or if not found. + /// A task that represents the asynchronous operation. The task result contains the result with category as a , or an error if not found. /// Thrown when the category ID is null or empty. - public async Task Handle(GetCategoryQuery query, CancellationToken cancellationToken = default) + public async Task> Handle(GetCategoryQuery query, CancellationToken cancellationToken = default) { - var result = await _repository.GetByIdAsync(query.CategoryId, cancellationToken); - return result.Success ? result.Value : null; + return await _repository.GetByIdAsync(query.CategoryId, cancellationToken); } } diff --git a/src/Api/Handlers/Categories/UpdateCategoryHandler.cs b/src/Api/Handlers/Categories/UpdateCategoryHandler.cs index 564fba6..f6799db 100644 --- a/src/Api/Handlers/Categories/UpdateCategoryHandler.cs +++ b/src/Api/Handlers/Categories/UpdateCategoryHandler.cs @@ -40,18 +40,18 @@ public UpdateCategoryHandler(ICategoryRepository repository, UpdateCategoryValid /// /// The command containing the updated category information. /// A token to cancel the asynchronous operation. - /// A task that represents the asynchronous operation. The task result contains the updated category as a . + /// A task that represents the asynchronous operation. The task result contains the result with updated category as a . /// Thrown when the command fails validation. /// Thrown when the category is not found or cannot be updated. - public async Task Handle(UpdateCategoryCommand command, CancellationToken cancellationToken = default) + public async Task> Handle(UpdateCategoryCommand command, CancellationToken cancellationToken = default) { var validationResult = await _validator.ValidateAsync(command, cancellationToken); if (!validationResult.IsValid) - throw new ValidationException(validationResult.Errors); + return Result.Fail("Validation failed", ResultErrorCode.Validation); var getResult = await _repository.GetByIdAsync(command.Id, cancellationToken); if (!getResult.Success || getResult.Value is null) - throw new NotFoundException($"Category with ID '{command.Id}' was not found."); + return Result.Fail($"Category with ID '{command.Id}' was not found.", ResultErrorCode.NotFound); var updatedCategory = getResult.Value with { @@ -59,10 +59,6 @@ public async Task Handle(UpdateCategoryCommand command, Cancellatio CategoryDescription = command.CategoryDescription ?? string.Empty }; - var updateResult = await _repository.UpdateAsync(updatedCategory, cancellationToken); - if (!updateResult.Success) - throw new NotFoundException($"Category with ID '{command.Id}' could not be updated."); - - return updateResult.Value!; + return await _repository.UpdateAsync(updatedCategory, cancellationToken); } } diff --git a/src/Api/Handlers/Comments/CommentEndpoints.cs b/src/Api/Handlers/Comments/CommentEndpoints.cs index 6608e5d..0daf0cf 100644 --- a/src/Api/Handlers/Comments/CommentEndpoints.cs +++ b/src/Api/Handlers/Comments/CommentEndpoints.cs @@ -25,8 +25,8 @@ public static IEndpointRouteBuilder MapCommentEndpoints(this IEndpointRouteBuild if (!ObjectId.TryParse(id, out var objectId)) return Results.BadRequest("Invalid ID format"); var query = new GetCommentQuery(objectId); - var comment = await handler.Handle(query); - return comment is not null ? Results.Ok(comment) : Results.NotFound(); + var result = await handler.Handle(query); + return result.Success ? Results.Ok(result.Value) : Results.NotFound(); }) .WithName("GetComment") .WithSummary("Get a comment by ID") @@ -50,7 +50,9 @@ public static IEndpointRouteBuilder MapCommentEndpoints(this IEndpointRouteBuild return Results.BadRequest("Invalid ID format"); var commandWithId = command with { Id = objectId }; var result = await handler.Handle(commandWithId); - return result is not null ? Results.Ok(result) : Results.NotFound(); + if (!result.Success) + return result.ErrorCode == ResultErrorCode.NotFound ? Results.NotFound() : Results.BadRequest(result.Error); + return Results.Ok(result.Value); }) .WithName("UpdateComment") .WithSummary("Update an existing comment") @@ -65,7 +67,7 @@ public static IEndpointRouteBuilder MapCommentEndpoints(this IEndpointRouteBuild return Results.BadRequest("Invalid ID format"); var command = new DeleteCommentCommand { Id = objectId }; var result = await handler.Handle(command); - return result ? Results.NoContent() : Results.NotFound(); + return result.Success ? Results.NoContent() : Results.NotFound(); }) .WithName("DeleteComment") .WithSummary("Delete (archive) a comment") diff --git a/src/Api/Handlers/Comments/DeleteCommentHandler.cs b/src/Api/Handlers/Comments/DeleteCommentHandler.cs index 85aab31..e700df4 100644 --- a/src/Api/Handlers/Comments/DeleteCommentHandler.cs +++ b/src/Api/Handlers/Comments/DeleteCommentHandler.cs @@ -40,23 +40,23 @@ public DeleteCommentHandler(ICommentRepository repository, DeleteCommentValidato /// /// The command containing the comment ID to delete. /// A token to cancel the asynchronous operation. - /// if the comment was successfully archived; otherwise, . + /// A task that represents the asynchronous operation. The task result contains a result indicating success or failure. /// Thrown when the command fails validation. /// Thrown when the comment is not found. - public async Task Handle(DeleteCommentCommand command, CancellationToken cancellationToken = default) + public async Task> Handle(DeleteCommentCommand command, CancellationToken cancellationToken = default) { var validationResult = await _validator.ValidateAsync(command, cancellationToken); if (!validationResult.IsValid) - throw new ValidationException(validationResult.Errors); + return Result.Fail("Validation failed", ResultErrorCode.Validation); var getResult = await _repository.GetByIdAsync(command.Id, cancellationToken); if (getResult.Failure || getResult.Value is null) - throw new NotFoundException($"Comment with ID '{command.Id}' was not found."); + return Result.Fail($"Comment with ID '{command.Id}' was not found.", ResultErrorCode.NotFound); if (getResult.Value.Archived) - return true; + return Result.Ok(true); var archiveResult = await _repository.ArchiveAsync(command.Id, cancellationToken); - return archiveResult.Success; + return archiveResult.Success ? Result.Ok(true) : Result.Fail(archiveResult.Error!, archiveResult.ErrorCode); } } diff --git a/src/Api/Handlers/Comments/GetCommentHandler.cs b/src/Api/Handlers/Comments/GetCommentHandler.cs index 3c700c7..39ed687 100644 --- a/src/Api/Handlers/Comments/GetCommentHandler.cs +++ b/src/Api/Handlers/Comments/GetCommentHandler.cs @@ -39,11 +39,10 @@ public GetCommentHandler(ICommentRepository repository) /// /// The query containing the comment ID to retrieve. /// A token to cancel the asynchronous operation. - /// A task that represents the asynchronous operation. The task result contains the comment as a , or if not found. + /// A task that represents the asynchronous operation. The task result contains the result with comment as a , or an error if not found. /// Thrown when the comment ID is null or empty. - public async Task Handle(GetCommentQuery query, CancellationToken cancellationToken = default) + public async Task> Handle(GetCommentQuery query, CancellationToken cancellationToken = default) { - var result = await _repository.GetByIdAsync(query.CommentId, cancellationToken); - return result.Success ? result.Value : null; + return await _repository.GetByIdAsync(query.CommentId, cancellationToken); } } diff --git a/src/Api/Handlers/Comments/UpdateCommentHandler.cs b/src/Api/Handlers/Comments/UpdateCommentHandler.cs index f03c772..cb9d361 100644 --- a/src/Api/Handlers/Comments/UpdateCommentHandler.cs +++ b/src/Api/Handlers/Comments/UpdateCommentHandler.cs @@ -40,28 +40,24 @@ public UpdateCommentHandler(ICommentRepository repository, UpdateCommentValidato /// /// The command containing the updated comment information. /// A token to cancel the asynchronous operation. - /// A task that represents the asynchronous operation. The task result contains the updated comment as a . + /// A task that represents the asynchronous operation. The task result contains the result with updated comment as a . /// Thrown when the command fails validation. /// Thrown when the comment is not found or cannot be updated. - public async Task Handle(UpdateCommentCommand command, CancellationToken cancellationToken = default) + public async Task> Handle(UpdateCommentCommand command, CancellationToken cancellationToken = default) { var validationResult = await _validator.ValidateAsync(command, cancellationToken); if (!validationResult.IsValid) - throw new ValidationException(validationResult.Errors); + return Result.Fail("Validation failed", ResultErrorCode.Validation); var getResult = await _repository.GetByIdAsync(command.Id, cancellationToken); if (getResult.Failure || getResult.Value is null) - throw new NotFoundException($"Comment with ID '{command.Id}' was not found."); + return Result.Fail($"Comment with ID '{command.Id}' was not found.", ResultErrorCode.NotFound); var updatedComment = getResult.Value with { Title = command.Title }; - var updateResult = await _repository.UpdateAsync(updatedComment, cancellationToken); - if (updateResult.Failure) - throw new NotFoundException($"Comment with ID '{command.Id}' could not be updated."); - - return updateResult.Value!; + return await _repository.UpdateAsync(updatedComment, cancellationToken); } } diff --git a/src/Api/Handlers/Issues/DeleteIssueHandler.cs b/src/Api/Handlers/Issues/DeleteIssueHandler.cs index 17ec532..e4e4565 100644 --- a/src/Api/Handlers/Issues/DeleteIssueHandler.cs +++ b/src/Api/Handlers/Issues/DeleteIssueHandler.cs @@ -29,20 +29,20 @@ public DeleteIssueHandler(IIssueRepository repository, DeleteIssueValidator vali /// /// Handles the soft-deletion (archiving) of an issue. /// - public async Task Handle(DeleteIssueCommand command, CancellationToken cancellationToken = default) + public async Task> Handle(DeleteIssueCommand command, CancellationToken cancellationToken = default) { var validationResult = await _validator.ValidateAsync(command, cancellationToken); if (!validationResult.IsValid) - throw new ValidationException(validationResult.Errors); + return Result.Fail("Validation failed", ResultErrorCode.Validation); var getResult = await _repository.GetByIdAsync(command.Id, cancellationToken); if (getResult.Failure || getResult.Value is null) - throw new NotFoundException($"Issue with ID '{command.Id}' was not found."); + return Result.Fail($"Issue with ID '{command.Id}' was not found.", ResultErrorCode.NotFound); if (getResult.Value.Archived) - return true; + return Result.Ok(true); var archiveResult = await _repository.ArchiveAsync(command.Id, cancellationToken); - return archiveResult.Success; + return archiveResult.Success ? Result.Ok(true) : Result.Fail(archiveResult.Error!, archiveResult.ErrorCode); } } diff --git a/src/Api/Handlers/Issues/GetIssueHandler.cs b/src/Api/Handlers/Issues/GetIssueHandler.cs index 8792d28..16035fd 100644 --- a/src/Api/Handlers/Issues/GetIssueHandler.cs +++ b/src/Api/Handlers/Issues/GetIssueHandler.cs @@ -32,10 +32,9 @@ public GetIssueHandler(IIssueRepository repository) /// /// Handles the retrieval of a single issue. /// - public async Task Handle(GetIssueQuery query, CancellationToken cancellationToken = default) + public async Task> Handle(GetIssueQuery query, CancellationToken cancellationToken = default) { - var result = await _repository.GetByIdAsync(query.IssueId, cancellationToken); - return result.Success ? result.Value : null; + return await _repository.GetByIdAsync(query.IssueId, cancellationToken); } /// diff --git a/src/Api/Handlers/Issues/IssueEndpoints.cs b/src/Api/Handlers/Issues/IssueEndpoints.cs index e2248f7..bb12880 100644 --- a/src/Api/Handlers/Issues/IssueEndpoints.cs +++ b/src/Api/Handlers/Issues/IssueEndpoints.cs @@ -35,8 +35,8 @@ public static IEndpointRouteBuilder MapIssueEndpoints(this IEndpointRouteBuilder if (!ObjectId.TryParse(id, out var objectId)) return Results.BadRequest("Invalid ID format"); var query = new GetIssueQuery(objectId); - var issue = await handler.Handle(query); - return issue is not null ? Results.Ok(issue) : Results.NotFound(); + var result = await handler.Handle(query); + return result.Success ? Results.Ok(result.Value) : Results.NotFound(); }) .WithName("GetIssue") .WithSummary("Get an issue by ID") @@ -62,7 +62,11 @@ public static IEndpointRouteBuilder MapIssueEndpoints(this IEndpointRouteBuilder return Results.BadRequest("Invalid ID format"); var commandWithId = command with { Id = objectId }; var result = await handler.Handle(commandWithId); - return result is not null ? Results.Ok(result) : Results.NotFound(); + if (!result.Success) + return result.ErrorCode == ResultErrorCode.NotFound ? Results.NotFound() + : result.ErrorCode == ResultErrorCode.Conflict ? Results.Conflict(result.Error) + : Results.BadRequest(result.Error); + return Results.Ok(result.Value); }) .WithName("UpdateIssue") .WithSummary("Update an existing issue") @@ -78,7 +82,7 @@ public static IEndpointRouteBuilder MapIssueEndpoints(this IEndpointRouteBuilder return Results.BadRequest("Invalid ID format"); var command = new DeleteIssueCommand { Id = objectId }; var result = await handler.Handle(command); - return result ? Results.NoContent() : Results.NotFound(); + return result.Success ? Results.NoContent() : Results.NotFound(); }) .WithName("DeleteIssue") .WithSummary("Delete (archive) an issue") diff --git a/src/Api/Handlers/Issues/UpdateIssueHandler.cs b/src/Api/Handlers/Issues/UpdateIssueHandler.cs index 63fcf9a..3ab0379 100644 --- a/src/Api/Handlers/Issues/UpdateIssueHandler.cs +++ b/src/Api/Handlers/Issues/UpdateIssueHandler.cs @@ -29,18 +29,18 @@ public UpdateIssueHandler(IIssueRepository repository, UpdateIssueValidator vali /// /// Handles the update of an existing issue. /// - public async Task Handle(UpdateIssueCommand command, CancellationToken cancellationToken = default) + public async Task> Handle(UpdateIssueCommand command, CancellationToken cancellationToken = default) { var validationResult = await _validator.ValidateAsync(command, cancellationToken); if (!validationResult.IsValid) - throw new ValidationException(validationResult.Errors); + return Result.Fail("Validation failed", ResultErrorCode.Validation); var getResult = await _repository.GetByIdAsync(command.Id, cancellationToken); if (!getResult.Success || getResult.Value is null) - throw new NotFoundException($"Issue with ID '{command.Id}' was not found."); + return Result.Fail($"Issue with ID '{command.Id}' was not found.", ResultErrorCode.NotFound); if (getResult.Value.Archived) - throw new ConflictException($"Issue with ID '{command.Id}' is archived and cannot be updated."); + return Result.Fail($"Issue with ID '{command.Id}' is archived and cannot be updated.", ResultErrorCode.Conflict); var updatedIssue = getResult.Value with { @@ -48,10 +48,6 @@ public async Task Handle(UpdateIssueCommand command, CancellationToken Description = command.Description ?? string.Empty }; - var updateResult = await _repository.UpdateAsync(updatedIssue, cancellationToken); - if (!updateResult.Success || updateResult.Value is null) - throw new NotFoundException($"Issue with ID '{command.Id}' could not be updated."); - - return updateResult.Value; + return await _repository.UpdateAsync(updatedIssue, cancellationToken); } } diff --git a/src/Api/Handlers/Issues/UpdateIssueStatusHandler.cs b/src/Api/Handlers/Issues/UpdateIssueStatusHandler.cs index 2007d2e..1d5ac19 100644 --- a/src/Api/Handlers/Issues/UpdateIssueStatusHandler.cs +++ b/src/Api/Handlers/Issues/UpdateIssueStatusHandler.cs @@ -29,18 +29,17 @@ public UpdateIssueStatusHandler(IIssueRepository repository, UpdateIssueStatusVa /// /// Handles the update of issue status. /// - public async Task Handle(UpdateIssueStatusCommand command, CancellationToken cancellationToken = default) + public async Task> Handle(UpdateIssueStatusCommand command, CancellationToken cancellationToken = default) { var validationResult = await _validator.ValidateAsync(command, cancellationToken); if (!validationResult.IsValid) - throw new ValidationException(validationResult.Errors); + return Result.Fail("Validation failed", ResultErrorCode.Validation); var result = await _repository.GetByIdAsync(command.IssueId, cancellationToken); if (!result.Success || result.Value is null) - return null; + return Result.Fail($"Issue with ID '{command.IssueId}' was not found.", ResultErrorCode.NotFound); var updatedIssue = result.Value with { Status = command.Status }; - var updateResult = await _repository.UpdateAsync(updatedIssue, cancellationToken); - return updateResult.Value; + return await _repository.UpdateAsync(updatedIssue, cancellationToken); } } diff --git a/src/Api/Handlers/Statuses/DeleteStatusHandler.cs b/src/Api/Handlers/Statuses/DeleteStatusHandler.cs index b331a1c..42b3813 100644 --- a/src/Api/Handlers/Statuses/DeleteStatusHandler.cs +++ b/src/Api/Handlers/Statuses/DeleteStatusHandler.cs @@ -40,23 +40,23 @@ public DeleteStatusHandler(IStatusRepository repository, DeleteStatusValidator v /// /// The command containing the status ID to delete. /// A token to cancel the asynchronous operation. - /// if the status was successfully archived; otherwise, . + /// A task that represents the asynchronous operation. The task result contains a result indicating success or failure. /// Thrown when the command fails validation. /// Thrown when the status is not found. - public async Task Handle(DeleteStatusCommand command, CancellationToken cancellationToken = default) + public async Task> Handle(DeleteStatusCommand command, CancellationToken cancellationToken = default) { var validationResult = await _validator.ValidateAsync(command, cancellationToken); if (!validationResult.IsValid) - throw new ValidationException(validationResult.Errors); + return Result.Fail("Validation failed", ResultErrorCode.Validation); var getResult = await _repository.GetByIdAsync(command.Id, cancellationToken); if (getResult.Failure || getResult.Value is null) - throw new NotFoundException($"Status with ID '{command.Id}' was not found."); + return Result.Fail($"Status with ID '{command.Id}' was not found.", ResultErrorCode.NotFound); if (getResult.Value.Archived) - return true; + return Result.Ok(true); var archiveResult = await _repository.ArchiveAsync(command.Id, cancellationToken); - return archiveResult.Success; + return archiveResult.Success ? Result.Ok(true) : Result.Fail(archiveResult.Error!, archiveResult.ErrorCode); } } diff --git a/src/Api/Handlers/Statuses/GetStatusHandler.cs b/src/Api/Handlers/Statuses/GetStatusHandler.cs index 05dc88f..f52d6c6 100644 --- a/src/Api/Handlers/Statuses/GetStatusHandler.cs +++ b/src/Api/Handlers/Statuses/GetStatusHandler.cs @@ -39,11 +39,10 @@ public GetStatusHandler(IStatusRepository repository) /// /// The query containing the status ID to retrieve. /// A token to cancel the asynchronous operation. - /// A task that represents the asynchronous operation. The task result contains the status as a , or if not found. + /// A task that represents the asynchronous operation. The task result contains the result with status as a , or an error if not found. /// Thrown when the status ID is null or empty. - public async Task Handle(GetStatusQuery query, CancellationToken cancellationToken = default) + public async Task> Handle(GetStatusQuery query, CancellationToken cancellationToken = default) { - var result = await _repository.GetByIdAsync(query.StatusId, cancellationToken); - return result.Success ? result.Value : null; + return await _repository.GetByIdAsync(query.StatusId, cancellationToken); } } diff --git a/src/Api/Handlers/Statuses/StatusEndpoints.cs b/src/Api/Handlers/Statuses/StatusEndpoints.cs index e6a58e1..330c06e 100644 --- a/src/Api/Handlers/Statuses/StatusEndpoints.cs +++ b/src/Api/Handlers/Statuses/StatusEndpoints.cs @@ -25,8 +25,8 @@ public static IEndpointRouteBuilder MapStatusEndpoints(this IEndpointRouteBuilde if (!ObjectId.TryParse(id, out var objectId)) return Results.BadRequest("Invalid ID format"); var query = new GetStatusQuery(objectId); - var status = await handler.Handle(query); - return status is not null ? Results.Ok(status) : Results.NotFound(); + var result = await handler.Handle(query); + return result.Success ? Results.Ok(result.Value) : Results.NotFound(); }) .WithName("GetStatus") .WithSummary("Get a status by ID") @@ -50,7 +50,9 @@ public static IEndpointRouteBuilder MapStatusEndpoints(this IEndpointRouteBuilde return Results.BadRequest("Invalid ID format"); var commandWithId = command with { Id = objectId }; var result = await handler.Handle(commandWithId); - return result is not null ? Results.Ok(result) : Results.NotFound(); + if (!result.Success) + return result.ErrorCode == ResultErrorCode.NotFound ? Results.NotFound() : Results.BadRequest(result.Error); + return Results.Ok(result.Value); }) .WithName("UpdateStatus") .WithSummary("Update an existing status") @@ -65,7 +67,7 @@ public static IEndpointRouteBuilder MapStatusEndpoints(this IEndpointRouteBuilde return Results.BadRequest("Invalid ID format"); var command = new DeleteStatusCommand { Id = objectId }; var result = await handler.Handle(command); - return result ? Results.NoContent() : Results.NotFound(); + return result.Success ? Results.NoContent() : Results.NotFound(); }) .WithName("DeleteStatus") .WithSummary("Delete (archive) a status") diff --git a/src/Api/Handlers/Statuses/UpdateStatusHandler.cs b/src/Api/Handlers/Statuses/UpdateStatusHandler.cs index 81f2ec3..8ff6721 100644 --- a/src/Api/Handlers/Statuses/UpdateStatusHandler.cs +++ b/src/Api/Handlers/Statuses/UpdateStatusHandler.cs @@ -40,18 +40,18 @@ public UpdateStatusHandler(IStatusRepository repository, UpdateStatusValidator v /// /// The command containing the updated status information. /// A token to cancel the asynchronous operation. - /// A task that represents the asynchronous operation. The task result contains the updated status as a . + /// A task that represents the asynchronous operation. The task result contains the result with updated status as a . /// Thrown when the command fails validation. /// Thrown when the status is not found or cannot be updated. - public async Task Handle(UpdateStatusCommand command, CancellationToken cancellationToken = default) + public async Task> Handle(UpdateStatusCommand command, CancellationToken cancellationToken = default) { var validationResult = await _validator.ValidateAsync(command, cancellationToken); if (!validationResult.IsValid) - throw new ValidationException(validationResult.Errors); + return Result.Fail("Validation failed", ResultErrorCode.Validation); var getResult = await _repository.GetByIdAsync(command.Id, cancellationToken); if (getResult.Failure || getResult.Value is null) - throw new NotFoundException($"Status with ID '{command.Id}' was not found."); + return Result.Fail($"Status with ID '{command.Id}' was not found.", ResultErrorCode.NotFound); var updatedStatus = getResult.Value with { @@ -59,10 +59,6 @@ public async Task Handle(UpdateStatusCommand command, CancellationTok StatusDescription = command.StatusDescription ?? string.Empty }; - var updateResult = await _repository.UpdateAsync(updatedStatus, cancellationToken); - if (updateResult.Failure) - throw new NotFoundException($"Status with ID '{command.Id}' could not be updated."); - - return updateResult.Value!; + return await _repository.UpdateAsync(updatedStatus, cancellationToken); } } From 6a17ad5da5a11ec9dbdc550a26637ec32be83552 Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 4 Mar 2026 10:41:33 -0800 Subject: [PATCH 2/2] test: update handler tests for Result return types (closes #82, #84, #86, #88) Updated 25 test files across Unit.Tests and Integration.Tests to assert on Result return values. Get/Delete/Update handlers in all 4 domains now verified via result.Success, result.Value, and result.ErrorCode assertions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../inbox/sam-result-handlers-complete.md | 121 ++++++++++++++++++ .../DeleteCategoryHandlerIntegrationTests.cs | 12 +- .../DeleteCommentHandlerIntegrationTests.cs | 12 +- .../DeleteStatusHandlerIntegrationTests.cs | 10 +- .../GetCategoryHandlerIntegrationTests.cs | 10 +- .../GetCommentHandlerIntegrationTests.cs | 15 +-- .../Handlers/GetIssueHandlerTests.cs | 12 +- .../GetStatusHandlerIntegrationTests.cs | 10 +- .../UpdateCategoryHandlerIntegrationTests.cs | 17 ++- .../UpdateCommentHandlerIntegrationTests.cs | 15 +-- .../UpdateIssueHandlerIntegrationTests.cs | 16 +-- .../Handlers/UpdateIssueStatusHandlerTests.cs | 21 +-- .../UpdateStatusHandlerIntegrationTests.cs | 17 ++- .../Categories/DeleteCategoryHandlerTests.cs | 22 ++-- .../Categories/GetCategoryHandlerTests.cs | 16 ++- .../Categories/UpdateCategoryHandlerTests.cs | 41 +++--- .../Comments/DeleteCommentHandlerTests.cs | 22 ++-- .../Comments/GetCommentHandlerTests.cs | 12 +- .../Comments/UpdateCommentHandlerTests.cs | 44 +++---- .../Issues/DeleteIssueHandlerTests.cs | 27 ++-- .../Handlers/Issues/GetIssueHandlerTests.cs | 15 ++- .../Issues/UpdateIssueHandlerTests.cs | 68 +++++----- .../Issues/UpdateIssueStatusHandlerTests.cs | 25 ++-- .../Statuses/DeleteStatusHandlerTests.cs | 21 +-- .../Statuses/GetStatusHandlerTests.cs | 13 +- .../Statuses/UpdateStatusHandlerTests.cs | 37 +++--- 26 files changed, 405 insertions(+), 246 deletions(-) create mode 100644 .squad/decisions/inbox/sam-result-handlers-complete.md diff --git a/.squad/decisions/inbox/sam-result-handlers-complete.md b/.squad/decisions/inbox/sam-result-handlers-complete.md new file mode 100644 index 0000000..d62517b --- /dev/null +++ b/.squad/decisions/inbox/sam-result-handlers-complete.md @@ -0,0 +1,121 @@ +# Result Handler Propagation Complete + +**Date**: 2025-01-02 +**Author**: Sam (Backend Developer) +**Issues**: #81, #83, #85, #87 +**Branch**: `squad/81-result-t-handlers` +**Commit**: `9885078` + +## Decision + +All API handlers have been updated to propagate `Result` from repositories to endpoints, completing the Result pattern implementation across all four domains (Issues, Categories, Statuses, Comments). + +## Implementation Details + +### Handler Changes + +**Return Type Changes**: +- Get handlers: `Task` → `Task>` +- Update handlers: `Task` → `Task>` +- Delete handlers: `Task` → `Task>` + +**Error Handling Pattern**: +```csharp +// Validation errors +if (!validationResult.IsValid) + return Result.Fail("Validation failed", ResultErrorCode.Validation); + +// Not found errors +if (getResult.Failure || getResult.Value is null) + return Result.Fail($"Entity with ID '{id}' was not found.", ResultErrorCode.NotFound); + +// Conflict errors (archived entities) +if (entity.Archived) + return Result.Fail($"Entity is archived.", ResultErrorCode.Conflict); + +// Propagate repository results +return await _repository.UpdateAsync(entity, cancellationToken); +``` + +**Delete Handler Pattern** (special case): +```csharp +// Repository.ArchiveAsync returns Result, not Result +var archiveResult = await _repository.ArchiveAsync(id, cancellationToken); +return archiveResult.Success + ? Result.Ok(true) + : Result.Fail(archiveResult.Error!, archiveResult.ErrorCode); +``` + +### Endpoint Changes + +**HTTP Response Mapping**: +```csharp +// Get endpoints +var result = await handler.Handle(query); +return result.Success ? Results.Ok(result.Value) : Results.NotFound(); + +// Update endpoints +var result = await handler.Handle(command); +if (!result.Success) + return result.ErrorCode == ResultErrorCode.NotFound ? Results.NotFound() + : result.ErrorCode == ResultErrorCode.Conflict ? Results.Conflict(result.Error) + : Results.BadRequest(result.Error); +return Results.Ok(result.Value); + +// Delete endpoints +var result = await handler.Handle(command); +return result.Success ? Results.NoContent() : Results.NotFound(); +``` + +## Files Changed + +### Issues (#81) +- `src/Api/Handlers/Issues/GetIssueHandler.cs` +- `src/Api/Handlers/Issues/DeleteIssueHandler.cs` +- `src/Api/Handlers/Issues/UpdateIssueHandler.cs` +- `src/Api/Handlers/Issues/UpdateIssueStatusHandler.cs` +- `src/Api/Handlers/Issues/IssueEndpoints.cs` + +### Categories (#83) +- `src/Api/Handlers/Categories/GetCategoryHandler.cs` +- `src/Api/Handlers/Categories/DeleteCategoryHandler.cs` +- `src/Api/Handlers/Categories/UpdateCategoryHandler.cs` +- `src/Api/Handlers/Categories/CategoryEndpoints.cs` + +### Statuses (#85) +- `src/Api/Handlers/Statuses/GetStatusHandler.cs` +- `src/Api/Handlers/Statuses/DeleteStatusHandler.cs` +- `src/Api/Handlers/Statuses/UpdateStatusHandler.cs` +- `src/Api/Handlers/Statuses/StatusEndpoints.cs` + +### Comments (#87) +- `src/Api/Handlers/Comments/GetCommentHandler.cs` +- `src/Api/Handlers/Comments/DeleteCommentHandler.cs` +- `src/Api/Handlers/Comments/UpdateCommentHandler.cs` +- `src/Api/Handlers/Comments/CommentEndpoints.cs` + +## Testing Status + +- ✅ **src/ compiles successfully** — No compilation errors in production code +- ❌ **tests/ have compilation errors** — Expected, waiting for Gimli to update test assertions + +## Blocked Work + +**Gimli** must update all handler tests to: +1. Expect `Result` return types instead of direct DTOs +2. Assert on `result.Success` and `result.Failure` +3. Access values via `result.Value` +4. Check error codes via `result.ErrorCode` +5. Update FluentAssertions patterns (e.g., `result.Success.Should().BeTrue()`) + +## PR Status + +⚠️ **DO NOT MERGE** until test suite is updated by Gimli. + +Branch: `squad/81-result-t-handlers` +PR: https://github.com/mpaulosky/IssueManager/pull/new/squad/81-result-t-handlers + +## Related Decisions + +- `.squad/decisions/inbox/aragorn-objectid-result-pattern.md` — Original Result pattern decision +- `.squad/decisions/inbox/sam-objectid-endpoint-parsing.md` — ObjectId parsing at endpoints (sprint #80) diff --git a/tests/Integration.Tests/Handlers/DeleteCategoryHandlerIntegrationTests.cs b/tests/Integration.Tests/Handlers/DeleteCategoryHandlerIntegrationTests.cs index 8eef5ed..2d6f7b2 100644 --- a/tests/Integration.Tests/Handlers/DeleteCategoryHandlerIntegrationTests.cs +++ b/tests/Integration.Tests/Handlers/DeleteCategoryHandlerIntegrationTests.cs @@ -60,7 +60,8 @@ public async Task Handle_ValidCategory_SetsArchivedInDatabase() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); // Verify Archived is set in a database var getResult = await _repository.GetByIdAsync(created.Value.Id, TestContext.Current.CancellationToken); @@ -76,10 +77,10 @@ public async Task Handle_NonExistentCategory_ThrowsNotFoundException() var command = new DeleteCategoryCommand { Id = nonExistentId }; // Act - Func act = async () => await _handler.Handle(command, TestContext.Current.CancellationToken); + var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - await act.Should().ThrowAsync(); + result.Success.Should().BeFalse(); } [Fact] @@ -95,9 +96,10 @@ public async Task Handle_AlreadyArchivedCategory_IsIdempotent() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - Should still return true - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); - var dbCategoryResult = await _repository.GetByIdAsync(created.Value!.Id, TestContext.Current.CancellationToken); + var dbCategoryResult= await _repository.GetByIdAsync(created.Value!.Id, TestContext.Current.CancellationToken); dbCategoryResult.Should().NotBeNull(); dbCategoryResult.Value!.Archived.Should().BeTrue(); } diff --git a/tests/Integration.Tests/Handlers/DeleteCommentHandlerIntegrationTests.cs b/tests/Integration.Tests/Handlers/DeleteCommentHandlerIntegrationTests.cs index 3142c8f..5d2f68d 100644 --- a/tests/Integration.Tests/Handlers/DeleteCommentHandlerIntegrationTests.cs +++ b/tests/Integration.Tests/Handlers/DeleteCommentHandlerIntegrationTests.cs @@ -61,7 +61,8 @@ public async Task Handle_ValidComment_SetsArchivedInDatabase() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); // Verify Archived is set in a database var getResult = await _repository.GetByIdAsync(created.Value.Id, TestContext.Current.CancellationToken); @@ -77,10 +78,10 @@ public async Task Handle_NonExistentComment_ThrowsNotFoundException() var command = new DeleteCommentCommand { Id = nonExistentId }; // Act - Func act = async () => await _handler.Handle(command, TestContext.Current.CancellationToken); + var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - await act.Should().ThrowAsync(); + result.Success.Should().BeFalse(); } [Fact] @@ -96,9 +97,10 @@ public async Task Handle_AlreadyArchivedComment_IsIdempotent() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - Should still return true - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); - var dbCommentResult = await _repository.GetByIdAsync(created.Value.Id, TestContext.Current.CancellationToken); + var dbCommentResult= await _repository.GetByIdAsync(created.Value.Id, TestContext.Current.CancellationToken); dbCommentResult.Should().NotBeNull(); dbCommentResult.Value?.Archived.Should().BeTrue(); } diff --git a/tests/Integration.Tests/Handlers/DeleteStatusHandlerIntegrationTests.cs b/tests/Integration.Tests/Handlers/DeleteStatusHandlerIntegrationTests.cs index 7b38c4c..4cb8692 100644 --- a/tests/Integration.Tests/Handlers/DeleteStatusHandlerIntegrationTests.cs +++ b/tests/Integration.Tests/Handlers/DeleteStatusHandlerIntegrationTests.cs @@ -60,7 +60,8 @@ public async Task Handle_ValidStatus_SetsArchivedInDatabase() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); // Verify Archived is set in a database var getResult = await _repository.GetByIdAsync(created.Value.Id, TestContext.Current.CancellationToken); @@ -76,10 +77,10 @@ public async Task Handle_NonExistentStatus_ThrowsNotFoundException() var command = new DeleteStatusCommand { Id = nonExistentId }; // Act - Func act = async () => await _handler.Handle(command, TestContext.Current.CancellationToken); + var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - await act.Should().ThrowAsync(); + result.Success.Should().BeFalse(); } [Fact] @@ -95,7 +96,8 @@ public async Task Handle_AlreadyArchivedStatus_IsIdempotent() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - Should still return true - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); var dbStatusResult = await _repository.GetByIdAsync(created.Value.Id, TestContext.Current.CancellationToken); dbStatusResult.Should().NotBeNull(); diff --git a/tests/Integration.Tests/Handlers/GetCategoryHandlerIntegrationTests.cs b/tests/Integration.Tests/Handlers/GetCategoryHandlerIntegrationTests.cs index 3624507..86621f0 100644 --- a/tests/Integration.Tests/Handlers/GetCategoryHandlerIntegrationTests.cs +++ b/tests/Integration.Tests/Handlers/GetCategoryHandlerIntegrationTests.cs @@ -60,9 +60,9 @@ public async Task Handle_ExistingCategory_ReturnsCategory() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().NotBeNull(); - result.CategoryName.Should().Be("Test Category"); - result.CategoryDescription.Should().Be("Test Description"); + result.Success.Should().BeTrue(); + result.Value!.CategoryName.Should().Be("Test Category"); + result.Value!.CategoryDescription.Should().Be("Test Description"); } [Fact] @@ -76,7 +76,7 @@ public async Task Handle_NonExistentCategory_ReturnsNull() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); } [Fact] @@ -89,6 +89,6 @@ public async Task Handle_EmptyObjectId_ReturnsNull() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); } } diff --git a/tests/Integration.Tests/Handlers/GetCommentHandlerIntegrationTests.cs b/tests/Integration.Tests/Handlers/GetCommentHandlerIntegrationTests.cs index 827a99c..8d473bc 100644 --- a/tests/Integration.Tests/Handlers/GetCommentHandlerIntegrationTests.cs +++ b/tests/Integration.Tests/Handlers/GetCommentHandlerIntegrationTests.cs @@ -61,9 +61,9 @@ public async Task Handle_ExistingComment_ReturnsComment() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().NotBeNull(); - result.Title.Should().Be("Test Comment"); - result.Description.Should().Be("Test Description"); + result.Success.Should().BeTrue(); + result.Value!.Title.Should().Be("Test Comment"); + result.Value!.Description.Should().Be("Test Description"); } [Fact] @@ -77,7 +77,7 @@ public async Task Handle_NonExistentComment_ReturnsNull() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); } [Fact] @@ -90,7 +90,7 @@ public async Task Handle_InvalidObjectIdFormat_ReturnsNull() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); } [Fact] @@ -100,10 +100,9 @@ public async Task Handle_EmptyId_ThrowsArgumentException() var query = new GetCommentQuery(ObjectId.Empty); // Act - Func act = async () => await _handler.Handle(query, TestContext.Current.CancellationToken); + var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - await act.Should().ThrowAsync() - .WithMessage("Comment ID cannot be empty.*"); + result.Success.Should().BeFalse(); } } diff --git a/tests/Integration.Tests/Handlers/GetIssueHandlerTests.cs b/tests/Integration.Tests/Handlers/GetIssueHandlerTests.cs index bf3e449..828e94c 100644 --- a/tests/Integration.Tests/Handlers/GetIssueHandlerTests.cs +++ b/tests/Integration.Tests/Handlers/GetIssueHandlerTests.cs @@ -58,10 +58,10 @@ public async Task Handle_ExistingIssueId_ReturnsIssue() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().NotBeNull(); - result!.Id.Should().Be(created.Value.Id); - result.Title.Should().Be("Test Issue"); - result.Description.Should().Be("Test Description"); + result.Success.Should().BeTrue(); + result.Value!.Id.Should().Be(created.Value.Id); + result.Value!.Title.Should().Be("Test Issue"); + result.Value!.Description.Should().Be("Test Description"); } [Fact] @@ -74,7 +74,7 @@ public async Task Handle_NonExistingIssueId_ReturnsNull() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); } [Fact] @@ -87,7 +87,7 @@ public async Task Handle_EmptyObjectId_ReturnsNull() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); } [Fact] diff --git a/tests/Integration.Tests/Handlers/GetStatusHandlerIntegrationTests.cs b/tests/Integration.Tests/Handlers/GetStatusHandlerIntegrationTests.cs index 62327ea..0fd025d 100644 --- a/tests/Integration.Tests/Handlers/GetStatusHandlerIntegrationTests.cs +++ b/tests/Integration.Tests/Handlers/GetStatusHandlerIntegrationTests.cs @@ -60,9 +60,9 @@ public async Task Handle_ExistingStatus_ReturnsStatus() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().NotBeNull(); - result.StatusName.Should().Be("Test Status"); - result.StatusDescription.Should().Be("Test Description"); + result.Success.Should().BeTrue(); + result.Value!.StatusName.Should().Be("Test Status"); + result.Value!.StatusDescription.Should().Be("Test Description"); } [Fact] @@ -76,7 +76,7 @@ public async Task Handle_NonExistentStatus_ReturnsNull() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); } [Fact] @@ -89,6 +89,6 @@ public async Task Handle_EmptyObjectId_ReturnsNull() var result = await _handler.Handle(query, TestContext.Current.CancellationToken); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); } } diff --git a/tests/Integration.Tests/Handlers/UpdateCategoryHandlerIntegrationTests.cs b/tests/Integration.Tests/Handlers/UpdateCategoryHandlerIntegrationTests.cs index 4d5a23c..ae8bd33 100644 --- a/tests/Integration.Tests/Handlers/UpdateCategoryHandlerIntegrationTests.cs +++ b/tests/Integration.Tests/Handlers/UpdateCategoryHandlerIntegrationTests.cs @@ -65,10 +65,10 @@ public async Task Handle_ExistingCategory_UpdatesSuccessfully() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - result.Should().NotBeNull(); - result.CategoryName.Should().Be("Updated Name"); - result.CategoryDescription.Should().Be("Updated Description"); - result.Id.Should().Be(created.Value.Id); + result.Success.Should().BeTrue(); + result.Value!.CategoryName.Should().Be("Updated Name"); + result.Value!.CategoryDescription.Should().Be("Updated Description"); + result.Value!.Id.Should().Be(created.Value.Id); } [Fact] @@ -84,11 +84,10 @@ public async Task Handle_NonExistentCategory_ThrowsNotFoundException() }; // Act - Func act = async () => await _handler.Handle(command, TestContext.Current.CancellationToken); + var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - await act.Should().ThrowAsync() - .WithMessage($"Category with ID '{nonExistentId}' was not found."); + result.Success.Should().BeFalse(); } [Fact] @@ -106,9 +105,9 @@ public async Task Handle_InvalidCommand_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, TestContext.Current.CancellationToken); + var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - await act.Should().ThrowAsync(); + result.Success.Should().BeFalse(); } } diff --git a/tests/Integration.Tests/Handlers/UpdateCommentHandlerIntegrationTests.cs b/tests/Integration.Tests/Handlers/UpdateCommentHandlerIntegrationTests.cs index cc101f7..36c3ea5 100644 --- a/tests/Integration.Tests/Handlers/UpdateCommentHandlerIntegrationTests.cs +++ b/tests/Integration.Tests/Handlers/UpdateCommentHandlerIntegrationTests.cs @@ -66,9 +66,9 @@ public async Task Handle_ExistingComment_UpdatesSuccessfully() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - result.Should().NotBeNull(); - result.Title.Should().Be("Updated Title"); - result.Id.Should().Be(created.Value.Id); + result.Success.Should().BeTrue(); + result.Value!.Title.Should().Be("Updated Title"); + result.Value!.Id.Should().Be(created.Value.Id); } [Fact] @@ -84,11 +84,10 @@ public async Task Handle_NonExistentComment_ThrowsNotFoundException() }; // Act - Func act = async () => await _handler.Handle(command, TestContext.Current.CancellationToken); + var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - await act.Should().ThrowAsync() - .WithMessage($"Comment with ID '{nonExistentId}' was not found."); + result.Success.Should().BeFalse(); } [Fact] @@ -105,9 +104,9 @@ public async Task Handle_InvalidCommand_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, TestContext.Current.CancellationToken); + var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - await act.Should().ThrowAsync(); + result.Success.Should().BeFalse(); } } diff --git a/tests/Integration.Tests/Handlers/UpdateIssueHandlerIntegrationTests.cs b/tests/Integration.Tests/Handlers/UpdateIssueHandlerIntegrationTests.cs index 01d7c71..9cd3897 100644 --- a/tests/Integration.Tests/Handlers/UpdateIssueHandlerIntegrationTests.cs +++ b/tests/Integration.Tests/Handlers/UpdateIssueHandlerIntegrationTests.cs @@ -64,10 +64,10 @@ public async Task Handle_ValidUpdate_UpdatesIssueInDatabase() var result = await _handler.Handle(command, CancellationToken.None); // Assert -result.Should().NotBeNull(); -result.Id.Should().Be(created.Value!.Id); -result.Title.Should().Be("Updated Title"); -result.Description.Should().Be("Updated Description"); +result.Success.Should().BeTrue(); +result.Value!.Id.Should().Be(created.Value!.Id); +result.Value!.Title.Should().Be("Updated Title"); +result.Value!.Description.Should().Be("Updated Description"); // Verify in database var dbIssueResult = await _repository.GetByIdAsync(created.Value.Id, TestContext.Current.CancellationToken); @@ -115,10 +115,10 @@ public async Task Handle_NonExistentIssue_ThrowsNotFoundException() }; // Act -Func act = async () => await _handler.Handle(command, CancellationToken.None); +var result = await _handler.Handle(command, CancellationToken.None); // Assert -await act.Should().ThrowAsync(); +result.Success.Should().BeFalse(); } [Fact] @@ -170,10 +170,10 @@ public async Task Handle_ArchivedIssue_ThrowsConflictException() }; // Act -Func act = async () => await _handler.Handle(command, CancellationToken.None); +var result = await _handler.Handle(command, CancellationToken.None); // Assert -await act.Should().ThrowAsync(); +result.Success.Should().BeFalse(); // Verify the issue wasn't updated var dbIssueResult = await _repository.GetByIdAsync(created.Value!.Id, TestContext.Current.CancellationToken); diff --git a/tests/Integration.Tests/Handlers/UpdateIssueStatusHandlerTests.cs b/tests/Integration.Tests/Handlers/UpdateIssueStatusHandlerTests.cs index 450b62d..4d1a151 100644 --- a/tests/Integration.Tests/Handlers/UpdateIssueStatusHandlerTests.cs +++ b/tests/Integration.Tests/Handlers/UpdateIssueStatusHandlerTests.cs @@ -66,8 +66,8 @@ public async Task Handle_ValidCommand_UpdatesIssueStatus() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - result.Should().NotBeNull(); - result.Status.StatusName.Should().Be("InProgress"); + result.Success.Should().BeTrue(); + result.Value!.Status.StatusName.Should().Be("InProgress"); } // Verify persistence @@ -91,7 +91,7 @@ public async Task Handle_NonExistingIssue_ReturnsNull() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); } [Fact] @@ -104,8 +104,11 @@ public async Task Handle_EmptyIssueId_ThrowsValidationException() Status = new StatusDto(ObjectId.GenerateNewId(), "InProgress", "Issue is in progress", DateTime.UtcNow, null, false, UserDto.Empty) }; - // Act & Assert - await Assert.ThrowsAsync(() => _handler.Handle(command, TestContext.Current.CancellationToken)); + // Act + var result = await _handler.Handle(command, TestContext.Current.CancellationToken); + + // Assert + result.Success.Should().BeFalse(); } [Fact] @@ -124,8 +127,8 @@ public async Task Handle_StatusTransition_OpenToInProgressToClosed_UpdatesCorrec var inProgressResult = await _handler.Handle(inProgressCommand, TestContext.Current.CancellationToken); // Assert InProgress - inProgressResult.Should().NotBeNull(); - inProgressResult.Status.StatusName.Should().Be("InProgress"); + inProgressResult.Success.Should().BeTrue(); + inProgressResult.Value!.Status.StatusName.Should().Be("InProgress"); // Act - Transition to Closed var closedCommand = new UpdateIssueStatusCommand @@ -136,8 +139,8 @@ public async Task Handle_StatusTransition_OpenToInProgressToClosed_UpdatesCorrec var closedResult = await _handler.Handle(closedCommand, TestContext.Current.CancellationToken); // Assert Closed - closedResult.Should().NotBeNull(); - closedResult.Status.StatusName.Should().Be("Closed"); + closedResult.Success.Should().BeTrue(); + closedResult.Value!.Status.StatusName.Should().Be("Closed"); // Verify the final state in a database var retrievedResult = await _repository.GetByIdAsync(created.Value!.Id, TestContext.Current.CancellationToken); diff --git a/tests/Integration.Tests/Handlers/UpdateStatusHandlerIntegrationTests.cs b/tests/Integration.Tests/Handlers/UpdateStatusHandlerIntegrationTests.cs index 26b4a95..fd629f9 100644 --- a/tests/Integration.Tests/Handlers/UpdateStatusHandlerIntegrationTests.cs +++ b/tests/Integration.Tests/Handlers/UpdateStatusHandlerIntegrationTests.cs @@ -65,10 +65,10 @@ public async Task Handle_ExistingStatus_UpdatesSuccessfully() var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - result.Should().NotBeNull(); - result.StatusName.Should().Be("Updated Name"); - result.StatusDescription.Should().Be("Updated Description"); - result.Id.Should().Be(created.Value.Id); + result.Success.Should().BeTrue(); + result.Value!.StatusName.Should().Be("Updated Name"); + result.Value!.StatusDescription.Should().Be("Updated Description"); + result.Value!.Id.Should().Be(created.Value.Id); } [Fact] @@ -84,11 +84,10 @@ public async Task Handle_NonExistentStatus_ThrowsNotFoundException() }; // Act - Func act = async () => await _handler.Handle(command, TestContext.Current.CancellationToken); + var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - await act.Should().ThrowAsync() - .WithMessage($"Status with ID '{nonExistentId}' was not found."); + result.Success.Should().BeFalse(); } [Fact] @@ -106,9 +105,9 @@ public async Task Handle_InvalidCommand_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, TestContext.Current.CancellationToken); + var result = await _handler.Handle(command, TestContext.Current.CancellationToken); // Assert - await act.Should().ThrowAsync(); + result.Success.Should().BeFalse(); } } diff --git a/tests/Unit.Tests/Handlers/Categories/DeleteCategoryHandlerTests.cs b/tests/Unit.Tests/Handlers/Categories/DeleteCategoryHandlerTests.cs index 85303e5..299bf27 100644 --- a/tests/Unit.Tests/Handlers/Categories/DeleteCategoryHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Categories/DeleteCategoryHandlerTests.cs @@ -45,13 +45,14 @@ public async Task Handle_ValidCategory_SetsIsArchivedToTrue() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); await _repository.Received(1).GetByIdAsync(categoryId, Arg.Any()); await _repository.Received(1).ArchiveAsync(categoryId, Arg.Any()); } [Fact] - public async Task Handle_NonExistentCategory_ThrowsNotFoundException() + public async Task Handle_NonExistentCategory_ReturnsNotFoundResult() { // Arrange var categoryId = ObjectId.GenerateNewId(); @@ -61,11 +62,11 @@ public async Task Handle_NonExistentCategory_ThrowsNotFoundException() .Returns(Result.Fail("Not found")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage($"*{categoryId}*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.NotFound); } [Fact] @@ -84,23 +85,24 @@ public async Task Handle_AlreadyArchivedCategory_IsIdempotent() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); await _repository.Received(1).GetByIdAsync(categoryId, Arg.Any()); await _repository.DidNotReceive().ArchiveAsync(Arg.Any(), Arg.Any()); } [Fact] - public async Task Handle_InvalidId_ThrowsValidationException() + public async Task Handle_InvalidId_ReturnsValidationResult() { // Arrange var command = new DeleteCategoryCommand { Id = ObjectId.Empty }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Category ID*required*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] diff --git a/tests/Unit.Tests/Handlers/Categories/GetCategoryHandlerTests.cs b/tests/Unit.Tests/Handlers/Categories/GetCategoryHandlerTests.cs index ee47d46..8bff387 100644 --- a/tests/Unit.Tests/Handlers/Categories/GetCategoryHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Categories/GetCategoryHandlerTests.cs @@ -40,14 +40,14 @@ public async Task Handle_ValidCategoryId_ReturnsCategory() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().NotBeNull(); - result!.CategoryName.Should().Be("Bug"); - result.CategoryDescription.Should().Be("Bug reports"); + result.Success.Should().BeTrue(); + result.Value!.CategoryName.Should().Be("Bug"); + result.Value.CategoryDescription.Should().Be("Bug reports"); await _repository.Received(1).GetByIdAsync(categoryId, Arg.Any()); } [Fact] - public async Task Handle_NonExistentCategoryId_ReturnsNull() + public async Task Handle_NonExistentCategoryId_ReturnsFailResult() { // Arrange var categoryId = ObjectId.GenerateNewId(); @@ -60,11 +60,12 @@ public async Task Handle_NonExistentCategoryId_ReturnsNull() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); + result.Error.Should().Be("Not found"); } [Fact] - public async Task Handle_EmptyObjectId_ReturnsNull() + public async Task Handle_EmptyObjectId_ReturnsFailResult() { // Arrange _repository.GetByIdAsync(ObjectId.Empty, Arg.Any()) @@ -75,7 +76,8 @@ public async Task Handle_EmptyObjectId_ReturnsNull() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); + result.Error.Should().Be("Not found"); await _repository.Received(1).GetByIdAsync(ObjectId.Empty, Arg.Any()); } diff --git a/tests/Unit.Tests/Handlers/Categories/UpdateCategoryHandlerTests.cs b/tests/Unit.Tests/Handlers/Categories/UpdateCategoryHandlerTests.cs index 66ee93c..cf46ca6 100644 --- a/tests/Unit.Tests/Handlers/Categories/UpdateCategoryHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Categories/UpdateCategoryHandlerTests.cs @@ -51,16 +51,16 @@ public async Task Handle_ValidCommand_ReturnsUpdatedCategory() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().NotBeNull(); - result.CategoryName.Should().Be("Updated Name"); - result.CategoryDescription.Should().Be("Updated Description"); + result.Success.Should().BeTrue(); + result.Value!.CategoryName.Should().Be("Updated Name"); + result.Value.CategoryDescription.Should().Be("Updated Description"); await _repository.Received(1).UpdateAsync(Arg.Is(c => c.CategoryName == command.CategoryName && c.CategoryDescription == command.CategoryDescription), Arg.Any()); } [Fact] - public async Task Handle_EmptyCategoryName_ThrowsValidationException() + public async Task Handle_EmptyCategoryName_ReturnsValidationResult() { // Arrange var command = new UpdateCategoryCommand @@ -71,15 +71,15 @@ public async Task Handle_EmptyCategoryName_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Category name*required*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] - public async Task Handle_CategoryNameTooLong_ThrowsValidationException() + public async Task Handle_CategoryNameTooLong_ReturnsValidationResult() { // Arrange var command = new UpdateCategoryCommand @@ -90,15 +90,15 @@ public async Task Handle_CategoryNameTooLong_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Category name*100 characters*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] - public async Task Handle_NonExistentCategory_ThrowsNotFoundException() + public async Task Handle_NonExistentCategory_ReturnsNotFoundResult() { // Arrange var categoryId = ObjectId.GenerateNewId(); @@ -113,15 +113,15 @@ public async Task Handle_NonExistentCategory_ThrowsNotFoundException() .Returns(Result.Fail("Not found")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage($"*{categoryId}*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.NotFound); } [Fact] - public async Task Handle_RepositoryUpdateFails_ThrowsNotFoundException() + public async Task Handle_RepositoryUpdateFails_ReturnsFailResult() { // Arrange var categoryId = ObjectId.GenerateNewId(); @@ -141,11 +141,11 @@ public async Task Handle_RepositoryUpdateFails_ThrowsNotFoundException() .Returns(Result.Fail("Update failed")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*could not be updated*"); + result.Success.Should().BeFalse(); + result.Error.Should().Be("Update failed"); } [Fact] @@ -173,6 +173,7 @@ public async Task Handle_NullDescription_UsesEmptyString() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.CategoryDescription.Should().BeEmpty(); + result.Success.Should().BeTrue(); + result.Value!.CategoryDescription.Should().BeEmpty(); } } diff --git a/tests/Unit.Tests/Handlers/Comments/DeleteCommentHandlerTests.cs b/tests/Unit.Tests/Handlers/Comments/DeleteCommentHandlerTests.cs index b1f4f47..bde4ab6 100644 --- a/tests/Unit.Tests/Handlers/Comments/DeleteCommentHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Comments/DeleteCommentHandlerTests.cs @@ -57,13 +57,14 @@ public async Task Handle_ValidComment_SetsIsArchivedToTrue() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); await _repository.Received(1).GetByIdAsync(commentId, Arg.Any()); await _repository.Received(1).ArchiveAsync(commentId, Arg.Any()); } [Fact] - public async Task Handle_NonExistentComment_ThrowsNotFoundException() + public async Task Handle_NonExistentComment_ReturnsNotFoundResult() { // Arrange var commentId = ObjectId.GenerateNewId(); @@ -73,11 +74,11 @@ public async Task Handle_NonExistentComment_ThrowsNotFoundException() .Returns(Result.Fail("Not found")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage($"*{commentId}*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.NotFound); } [Fact] @@ -108,23 +109,24 @@ public async Task Handle_AlreadyArchivedComment_IsIdempotent() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); await _repository.Received(1).GetByIdAsync(commentId, Arg.Any()); await _repository.DidNotReceive().ArchiveAsync(Arg.Any(), Arg.Any()); } [Fact] - public async Task Handle_EmptyId_ThrowsValidationException() + public async Task Handle_EmptyId_ReturnsValidationFailure() { // Arrange var command = new DeleteCommentCommand { Id = ObjectId.Empty }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Comment ID*required*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] diff --git a/tests/Unit.Tests/Handlers/Comments/GetCommentHandlerTests.cs b/tests/Unit.Tests/Handlers/Comments/GetCommentHandlerTests.cs index 14d9a93..35bf778 100644 --- a/tests/Unit.Tests/Handlers/Comments/GetCommentHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Comments/GetCommentHandlerTests.cs @@ -52,9 +52,9 @@ public async Task Handle_ValidCommentId_ReturnsComment() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().NotBeNull(); - result!.Title.Should().Be("Test Comment"); - result.Description.Should().Be("This is a test comment."); + result.Success.Should().BeTrue(); + result.Value!.Title.Should().Be("Test Comment"); + result.Value.Description.Should().Be("This is a test comment."); await _repository.Received(1).GetByIdAsync(commentId, Arg.Any()); } @@ -72,7 +72,8 @@ public async Task Handle_NonExistentCommentId_ReturnsNull() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); + result.Error.Should().Be("Not found"); } [Fact] @@ -87,7 +88,8 @@ public async Task Handle_EmptyObjectId_ReturnsNull() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); + result.Error.Should().Be("Not found"); await _repository.Received(1).GetByIdAsync(ObjectId.Empty, Arg.Any()); } diff --git a/tests/Unit.Tests/Handlers/Comments/UpdateCommentHandlerTests.cs b/tests/Unit.Tests/Handlers/Comments/UpdateCommentHandlerTests.cs index 13b469b..96d859f 100644 --- a/tests/Unit.Tests/Handlers/Comments/UpdateCommentHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Comments/UpdateCommentHandlerTests.cs @@ -51,14 +51,14 @@ public async Task Handle_ValidCommand_ReturnsUpdatedComment() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().NotBeNull(); - result.Title.Should().Be("Updated Title"); + result.Success.Should().BeTrue(); + result.Value!.Title.Should().Be("Updated Title"); await _repository.Received(1).UpdateAsync(Arg.Is(c => c.Title == command.Title), Arg.Any()); } [Fact] - public async Task Handle_EmptyId_ThrowsValidationException() + public async Task Handle_EmptyId_ReturnsValidationFailure() { // Arrange var command = new UpdateCommentCommand @@ -69,15 +69,15 @@ public async Task Handle_EmptyId_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Comment ID*required*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] - public async Task Handle_EmptyCommentText_ThrowsValidationException() + public async Task Handle_EmptyCommentText_ReturnsValidationFailure() { // Arrange var command = new UpdateCommentCommand @@ -88,15 +88,15 @@ public async Task Handle_EmptyCommentText_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Comment text*required*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] - public async Task Handle_CommentTextTooLong_ThrowsValidationException() + public async Task Handle_CommentTextTooLong_ReturnsValidationFailure() { // Arrange var command = new UpdateCommentCommand @@ -107,15 +107,15 @@ public async Task Handle_CommentTextTooLong_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Comment text*5000 characters*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] - public async Task Handle_NonExistentComment_ThrowsNotFoundException() + public async Task Handle_NonExistentComment_ReturnsNotFoundResult() { // Arrange var commentId = ObjectId.GenerateNewId(); @@ -130,15 +130,15 @@ public async Task Handle_NonExistentComment_ThrowsNotFoundException() .Returns(Result.Fail("Not found")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage($"*{commentId}*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.NotFound); } [Fact] - public async Task Handle_RepositoryUpdateFails_ThrowsNotFoundException() + public async Task Handle_RepositoryUpdateFails_ReturnsFailureResult() { // Arrange var commentId = ObjectId.GenerateNewId(); @@ -158,10 +158,10 @@ public async Task Handle_RepositoryUpdateFails_ThrowsNotFoundException() .Returns(Result.Fail("Update failed")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*could not be updated*"); + result.Success.Should().BeFalse(); + result.Error.Should().Contain("Update failed"); } } diff --git a/tests/Unit.Tests/Handlers/Issues/DeleteIssueHandlerTests.cs b/tests/Unit.Tests/Handlers/Issues/DeleteIssueHandlerTests.cs index 231e25d..d054094 100644 --- a/tests/Unit.Tests/Handlers/Issues/DeleteIssueHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Issues/DeleteIssueHandlerTests.cs @@ -51,15 +51,16 @@ public async Task Handle_ValidIssue_SetsIsArchivedToTrue() .Returns(Result.Ok()); // Act - await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert + result.Success.Should().BeTrue(); await _repository.Received(1).GetByIdAsync(issueId, Arg.Any()); await _repository.Received(1).ArchiveAsync(issueId, Arg.Any()); } [Fact] - public async Task Handle_NonExistentIssue_ThrowsNotFoundException() + public async Task Handle_NonExistentIssue_ReturnsNotFoundFailure() { // Arrange var issueId = ObjectId.GenerateNewId(); @@ -69,11 +70,11 @@ public async Task Handle_NonExistentIssue_ThrowsNotFoundException() .Returns(Result.Fail("not found")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage($"*{issueId}*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.NotFound); } [Fact] @@ -101,8 +102,11 @@ public async Task Handle_AlreadyArchivedIssue_IsIdempotent() .Returns(Result.Ok(archivedIssue)); // Act — should succeed idempotently without calling ArchiveAsync - await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); + // Assert + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); await _repository.Received(1).GetByIdAsync(issueId, Arg.Any()); // Should NOT call ArchiveAsync since already archived await _repository.DidNotReceive().ArchiveAsync(Arg.Any(), Arg.Any()); @@ -139,21 +143,22 @@ public async Task Handle_ValidIssue_CallsArchive() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); await _repository.Received(1).ArchiveAsync(issueId, Arg.Any()); } [Fact] - public async Task Handle_InvalidId_ThrowsValidationException() + public async Task Handle_InvalidId_ReturnsValidationFailure() { // Arrange var command = new DeleteIssueCommand { Id = ObjectId.Empty }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Id*required*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } } diff --git a/tests/Unit.Tests/Handlers/Issues/GetIssueHandlerTests.cs b/tests/Unit.Tests/Handlers/Issues/GetIssueHandlerTests.cs index d48a236..e74b7eb 100644 --- a/tests/Unit.Tests/Handlers/Issues/GetIssueHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Issues/GetIssueHandlerTests.cs @@ -45,14 +45,14 @@ public async Task Handle_ValidIssueId_ReturnsIssue() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().NotBeNull(); - result!.Id.ToString().Should().Be(issueId.ToString()); - result.Title.Should().Be("Test Issue"); + result.Success.Should().BeTrue(); + result.Value!.Id.ToString().Should().Be(issueId.ToString()); + result.Value!.Title.Should().Be("Test Issue"); await _repository.Received(1).GetByIdAsync(issueId, Arg.Any()); } [Fact] - public async Task Handle_NonExistentIssueId_ReturnsNull() + public async Task Handle_NonExistentIssueId_ReturnsFailure() { // Arrange var issueId = ObjectId.GenerateNewId(); @@ -65,12 +65,13 @@ public async Task Handle_NonExistentIssueId_ReturnsNull() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); + result.Error.Should().Be("Not found"); await _repository.Received(1).GetByIdAsync(issueId, Arg.Any()); } [Fact] - public async Task Handle_EmptyObjectId_ReturnsNull() + public async Task Handle_EmptyObjectId_ReturnsFailure() { // Arrange _repository.GetByIdAsync(ObjectId.Empty, Arg.Any()) @@ -81,7 +82,7 @@ public async Task Handle_EmptyObjectId_ReturnsNull() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); } [Fact] diff --git a/tests/Unit.Tests/Handlers/Issues/UpdateIssueHandlerTests.cs b/tests/Unit.Tests/Handlers/Issues/UpdateIssueHandlerTests.cs index 012d3de..af6ea2d 100644 --- a/tests/Unit.Tests/Handlers/Issues/UpdateIssueHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Issues/UpdateIssueHandlerTests.cs @@ -52,7 +52,7 @@ public async Task Handle_ValidCommand_ReturnsUpdatedIssue() }; _repository.GetByIdAsync(issueId, Arg.Any()) - .Returns(Result.Ok(existingIssue)); + .Returns(Result.Ok(existingIssue)); var updatedIssue = existingIssue with { @@ -61,15 +61,15 @@ public async Task Handle_ValidCommand_ReturnsUpdatedIssue() }; _repository.UpdateAsync(Arg.Any(), Arg.Any()) - .Returns(Result.Ok(updatedIssue)); + .Returns(Result.Ok(updatedIssue)); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().NotBeNull(); - result.Title.Should().Be("Updated Title"); - result.Description.Should().Be("Updated Description"); + result.Success.Should().BeTrue(); + result.Value!.Title.Should().Be("Updated Title"); + result.Value!.Description.Should().Be("Updated Description"); await _repository.Received(1).GetByIdAsync(issueId, Arg.Any()); await _repository.Received(1).UpdateAsync(Arg.Is(i => @@ -78,7 +78,7 @@ await _repository.Received(1).UpdateAsync(Arg.Is(i => } [Fact] - public async Task Handle_EmptyTitle_ThrowsValidationException() + public async Task Handle_EmptyTitle_ReturnsValidationFailure() { // Arrange var command = new UpdateIssueCommand @@ -89,15 +89,15 @@ public async Task Handle_EmptyTitle_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Title*required*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] - public async Task Handle_TitleTooLong_ThrowsValidationException() + public async Task Handle_TitleTooLong_ReturnsValidationFailure() { // Arrange var command = new UpdateIssueCommand @@ -108,15 +108,15 @@ public async Task Handle_TitleTooLong_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Title*256*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] - public async Task Handle_DescriptionTooLong_ThrowsValidationException() + public async Task Handle_DescriptionTooLong_ReturnsValidationFailure() { // Arrange var command = new UpdateIssueCommand @@ -127,15 +127,15 @@ public async Task Handle_DescriptionTooLong_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Description*4096*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] - public async Task Handle_NonExistentIssue_ThrowsNotFoundException() + public async Task Handle_NonExistentIssue_ReturnsNotFoundFailure() { // Arrange var issueId = ObjectId.GenerateNewId(); @@ -147,18 +147,18 @@ public async Task Handle_NonExistentIssue_ThrowsNotFoundException() }; _repository.GetByIdAsync(issueId, Arg.Any()) - .Returns(Result.Fail("Not found")); + .Returns(Result.Fail("Not found")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage($"*{issueId}*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.NotFound); } [Fact] - public async Task Handle_ArchivedIssue_ThrowsConflictException() + public async Task Handle_ArchivedIssue_ReturnsConflictFailure() { // Arrange var issueId = ObjectId.GenerateNewId(); @@ -184,14 +184,14 @@ public async Task Handle_ArchivedIssue_ThrowsConflictException() }; _repository.GetByIdAsync(issueId, Arg.Any()) - .Returns(Result.Ok(archivedIssue)); + .Returns(Result.Ok(archivedIssue)); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*archived*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Conflict); } [Fact] @@ -221,19 +221,19 @@ public async Task Handle_IdempotentUpdate_ReturnsUpdatedIssue() }; _repository.GetByIdAsync(issueId, Arg.Any()) - .Returns(Result.Ok(existingIssue)); + .Returns(Result.Ok(existingIssue)); var updatedIssue = existingIssue with { }; _repository.UpdateAsync(Arg.Any(), Arg.Any()) - .Returns(Result.Ok(updatedIssue)); + .Returns(Result.Ok(updatedIssue)); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().NotBeNull(); - result.Title.Should().Be("Same Title"); + result.Success.Should().BeTrue(); + result.Value!.Title.Should().Be("Same Title"); } [Fact] @@ -263,7 +263,7 @@ public async Task Handle_NullDescription_UsesEmptyString() }; _repository.GetByIdAsync(issueId, Arg.Any()) - .Returns(Result.Ok(existingIssue)); + .Returns(Result.Ok(existingIssue)); var updatedIssue = existingIssue with { @@ -272,12 +272,12 @@ public async Task Handle_NullDescription_UsesEmptyString() }; _repository.UpdateAsync(Arg.Any(), Arg.Any()) - .Returns(Result.Ok(updatedIssue)); + .Returns(Result.Ok(updatedIssue)); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Description.Should().BeEmpty(); + result.Value!.Description.Should().BeEmpty(); } } diff --git a/tests/Unit.Tests/Handlers/Issues/UpdateIssueStatusHandlerTests.cs b/tests/Unit.Tests/Handlers/Issues/UpdateIssueStatusHandlerTests.cs index 2b0eb65..8168e45 100644 --- a/tests/Unit.Tests/Handlers/Issues/UpdateIssueStatusHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Issues/UpdateIssueStatusHandlerTests.cs @@ -56,14 +56,14 @@ public async Task Handle_ValidCommand_ReturnsUpdatedIssue() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().NotBeNull(); - result!.Status.StatusName.Should().Be("Closed"); + result.Success.Should().BeTrue(); + result.Value!.Status.StatusName.Should().Be("Closed"); await _repository.Received(1).GetByIdAsync(Arg.Any(), Arg.Any()); await _repository.Received(1).UpdateAsync(Arg.Is(i => i.Status.StatusName == "Closed"), Arg.Any()); } [Fact] - public async Task Handle_NonExistentIssue_ReturnsNull() + public async Task Handle_NonExistentIssue_ReturnsNotFoundFailure() { // Arrange var issueId = ObjectId.GenerateNewId(); @@ -80,13 +80,14 @@ public async Task Handle_NonExistentIssue_ReturnsNull() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().BeNull(); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.NotFound); await _repository.Received(1).GetByIdAsync(Arg.Any(), Arg.Any()); await _repository.DidNotReceive().UpdateAsync(Arg.Any(), Arg.Any()); } [Fact] - public async Task Handle_EmptyIssueId_ThrowsValidationException() + public async Task Handle_EmptyIssueId_ReturnsValidationFailure() { // Arrange var command = new UpdateIssueStatusCommand @@ -96,15 +97,15 @@ public async Task Handle_EmptyIssueId_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Issue ID*required*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] - public async Task Handle_EmptyStatusName_ThrowsValidationException() + public async Task Handle_EmptyStatusName_ReturnsValidationFailure() { // Arrange var command = new UpdateIssueStatusCommand @@ -114,11 +115,11 @@ public async Task Handle_EmptyStatusName_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Status name*required*"); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] diff --git a/tests/Unit.Tests/Handlers/Statuses/DeleteStatusHandlerTests.cs b/tests/Unit.Tests/Handlers/Statuses/DeleteStatusHandlerTests.cs index 650e495..6514fe2 100644 --- a/tests/Unit.Tests/Handlers/Statuses/DeleteStatusHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Statuses/DeleteStatusHandlerTests.cs @@ -52,7 +52,9 @@ public async Task Handle_ValidStatus_SetsIsArchivedToTrue() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().BeTrue(); + result.Should().NotBeNull(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); await _repository.Received(1).GetByIdAsync(statusId, Arg.Any()); await _repository.Received(1).ArchiveAsync(statusId, Arg.Any()); } @@ -68,11 +70,12 @@ public async Task Handle_NonExistentStatus_ThrowsNotFoundException() .Returns(Result.Fail("Not found")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage($"*{statusId}*"); + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.NotFound); } [Fact] @@ -98,7 +101,8 @@ public async Task Handle_AlreadyArchivedStatus_IsIdempotent() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.Should().BeTrue(); + result.Success.Should().BeTrue(); + result.Value.Should().BeTrue(); await _repository.Received(1).GetByIdAsync(statusId, Arg.Any()); await _repository.DidNotReceive().ArchiveAsync(Arg.Any(), Arg.Any()); } @@ -110,11 +114,12 @@ public async Task Handle_InvalidId_ThrowsValidationException() var command = new DeleteStatusCommand { Id = ObjectId.Empty }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Status ID*required*"); + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] diff --git a/tests/Unit.Tests/Handlers/Statuses/GetStatusHandlerTests.cs b/tests/Unit.Tests/Handlers/Statuses/GetStatusHandlerTests.cs index f06412b..84c81d6 100644 --- a/tests/Unit.Tests/Handlers/Statuses/GetStatusHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Statuses/GetStatusHandlerTests.cs @@ -41,8 +41,9 @@ public async Task Handle_ValidStatusId_ReturnsStatus() // Assert result.Should().NotBeNull(); - result!.StatusName.Should().Be("Open"); - result.StatusDescription.Should().Be("Issue is open"); + result.Success.Should().BeTrue(); + result.Value!.StatusName.Should().Be("Open"); + result.Value.StatusDescription.Should().Be("Issue is open"); await _repository.Received(1).GetByIdAsync(statusId, Arg.Any()); } @@ -60,7 +61,9 @@ public async Task Handle_NonExistentStatusId_ReturnsNull() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().BeNull(); + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Error.Should().Be("Not found"); } [Fact] @@ -75,7 +78,9 @@ public async Task Handle_EmptyObjectId_ReturnsNull() var result = await _handler.Handle(query, CancellationToken.None); // Assert - result.Should().BeNull(); + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Error.Should().Be("Not found"); await _repository.Received(1).GetByIdAsync(ObjectId.Empty, Arg.Any()); } diff --git a/tests/Unit.Tests/Handlers/Statuses/UpdateStatusHandlerTests.cs b/tests/Unit.Tests/Handlers/Statuses/UpdateStatusHandlerTests.cs index 44a10c6..e6af3db 100644 --- a/tests/Unit.Tests/Handlers/Statuses/UpdateStatusHandlerTests.cs +++ b/tests/Unit.Tests/Handlers/Statuses/UpdateStatusHandlerTests.cs @@ -64,8 +64,9 @@ public async Task Handle_ValidCommand_ReturnsUpdatedStatus() // Assert result.Should().NotBeNull(); - result.StatusName.Should().Be("Updated Name"); - result.StatusDescription.Should().Be("Updated Description"); + result.Success.Should().BeTrue(); + result.Value!.StatusName.Should().Be("Updated Name"); + result.Value.StatusDescription.Should().Be("Updated Description"); await _repository.Received(1).UpdateAsync(Arg.Is(s => s.StatusName == command.StatusName && s.StatusDescription == command.StatusDescription), Arg.Any()); @@ -83,11 +84,12 @@ public async Task Handle_EmptyStatusName_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Status name*required*"); + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] @@ -102,11 +104,12 @@ public async Task Handle_StatusNameTooLong_ThrowsValidationException() }; // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*Status name*100 characters*"); + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.Validation); } [Fact] @@ -125,11 +128,12 @@ public async Task Handle_NonExistentStatus_ThrowsNotFoundException() .Returns(Result.Fail("Not found")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage($"*{statusId}*"); + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.ErrorCode.Should().Be(ResultErrorCode.NotFound); } [Fact] @@ -160,11 +164,12 @@ public async Task Handle_RepositoryUpdateFails_ThrowsNotFoundException() .Returns(Result.Fail("Update failed")); // Act - Func act = async () => await _handler.Handle(command, CancellationToken.None); + var result = await _handler.Handle(command, CancellationToken.None); // Assert - await act.Should().ThrowAsync() - .WithMessage("*could not be updated*"); + result.Should().NotBeNull(); + result.Success.Should().BeFalse(); + result.Error.Should().Be("Update failed"); } [Fact] @@ -204,6 +209,8 @@ public async Task Handle_NullDescription_UsesEmptyString() var result = await _handler.Handle(command, CancellationToken.None); // Assert - result.StatusDescription.Should().BeEmpty(); + result.Should().NotBeNull(); + result.Success.Should().BeTrue(); + result.Value!.StatusDescription.Should().BeEmpty(); } }