diff --git a/src/Api/Data/Interfaces/IIssueRepository.cs b/src/Api/Data/Interfaces/IIssueRepository.cs index 323a10a..dc5650d 100644 --- a/src/Api/Data/Interfaces/IIssueRepository.cs +++ b/src/Api/Data/Interfaces/IIssueRepository.cs @@ -42,8 +42,10 @@ public interface IIssueRepository /// The number of items per page. /// Optional search term to filter by title or description. /// Optional author name to filter by. + /// Optional status name to filter by. + /// Optional category name to filter by. /// Cancellation token. - Task Items, long Total)>> GetAllAsync(int page, int pageSize, string? searchTerm = null, string? authorName = null, CancellationToken cancellationToken = default); + Task Items, long Total)>> GetAllAsync(int page, int pageSize, string? searchTerm = null, string? authorName = null, string? statusName = null, string? categoryName = null, CancellationToken cancellationToken = default); /// /// Updates an existing issue in the database. diff --git a/src/Api/Data/IssueRepository.cs b/src/Api/Data/IssueRepository.cs index 916ed94..da2a5cf 100644 --- a/src/Api/Data/IssueRepository.cs +++ b/src/Api/Data/IssueRepository.cs @@ -72,6 +72,8 @@ public async Task>> GetAllAsync(CancellationToken int pageSize, string? searchTerm = null, string? authorName = null, + string? statusName = null, + string? categoryName = null, CancellationToken cancellationToken = default) { var filterBuilder = Builders.Filter; @@ -94,6 +96,16 @@ public async Task>> GetAllAsync(CancellationToken filters.Add(filterBuilder.Regex(x => x.Author.Name, new BsonRegularExpression(authorName, "i"))); } + if (!string.IsNullOrWhiteSpace(statusName)) + { + filters.Add(filterBuilder.Regex(x => x.Status.StatusName, new BsonRegularExpression(statusName, "i"))); + } + + if (!string.IsNullOrWhiteSpace(categoryName)) + { + filters.Add(filterBuilder.Regex(x => x.Category.CategoryName, new BsonRegularExpression(categoryName, "i"))); + } + var filter = filterBuilder.And(filters); var total = await _collection.CountDocumentsAsync(filter, cancellationToken: cancellationToken); var entities = await _collection diff --git a/src/Api/Extensions/ServiceCollectionExtensions.cs b/src/Api/Extensions/ServiceCollectionExtensions.cs index c91b043..98ec467 100644 --- a/src/Api/Extensions/ServiceCollectionExtensions.cs +++ b/src/Api/Extensions/ServiceCollectionExtensions.cs @@ -73,10 +73,12 @@ public static IServiceCollection AddHandlers(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Api/Handlers/Categories/CategoryEndpoints.cs b/src/Api/Handlers/Categories/CategoryEndpoints.cs index 07061fa..5a2e50e 100644 --- a/src/Api/Handlers/Categories/CategoryEndpoints.cs +++ b/src/Api/Handlers/Categories/CategoryEndpoints.cs @@ -66,6 +66,20 @@ public static IEndpointRouteBuilder MapCategoryEndpoints(this IEndpointRouteBuil .Produces(StatusCodes.Status404NotFound) .RequireAuthorization(); + group.MapDelete("{id}", async (string id, DeleteCategoryHandler handler) => + { + if (!ObjectId.TryParse(id, out var objectId)) + return Results.BadRequest("Invalid ID format"); + var command = new DeleteCategoryCommand { Id = objectId }; + var result = await handler.Handle(command); + return result.Success ? Results.NoContent() : Results.NotFound(); + }) + .WithName("DeleteCategory") + .WithSummary("Delete (archive) a category") + .Produces(StatusCodes.Status204NoContent) + .Produces(StatusCodes.Status404NotFound) + .RequireAuthorization(); + return app; } } diff --git a/src/Api/Handlers/Issues/IssueEndpoints.cs b/src/Api/Handlers/Issues/IssueEndpoints.cs index 239b0f0..b41ee0e 100644 --- a/src/Api/Handlers/Issues/IssueEndpoints.cs +++ b/src/Api/Handlers/Issues/IssueEndpoints.cs @@ -21,14 +21,16 @@ public static IEndpointRouteBuilder MapIssueEndpoints(this IEndpointRouteBuilder var group = app.MapGroup("/api/v1/issues").WithTags("Issues"); // List Issues (paginated) - group.MapGet("", async (int? page, int? pageSize, string? searchTerm, string? authorName, ListIssuesHandler handler) => + group.MapGet("", async (int? page, int? pageSize, string? searchTerm, string? authorName, string? statusName, string? categoryName, ListIssuesHandler handler) => { var query = new ListIssuesQuery { Page = page ?? 1, PageSize = pageSize ?? 20, SearchTerm = searchTerm, - AuthorName = authorName + AuthorName = authorName, + StatusName = statusName, + CategoryName = categoryName }; var result = await handler.Handle(query); return Results.Ok(result); diff --git a/src/Api/Handlers/Issues/ListIssuesHandler.cs b/src/Api/Handlers/Issues/ListIssuesHandler.cs index 4c40615..92e1566 100644 --- a/src/Api/Handlers/Issues/ListIssuesHandler.cs +++ b/src/Api/Handlers/Issues/ListIssuesHandler.cs @@ -35,7 +35,7 @@ public async Task> Handle(ListIssuesQuery query, Can if (!validationResult.IsValid) throw new ValidationException(validationResult.Errors); - var result = await _repository.GetAllAsync(query.Page, query.PageSize, query.SearchTerm, query.AuthorName, cancellationToken); + var result = await _repository.GetAllAsync(query.Page, query.PageSize, query.SearchTerm, query.AuthorName, query.StatusName, query.CategoryName, cancellationToken); var (items, total) = result.Value; return new PaginatedResponse(items, total, query.Page, query.PageSize); diff --git a/src/Api/Handlers/Statuses/DeleteStatusHandler.cs b/src/Api/Handlers/Statuses/DeleteStatusHandler.cs index 961cb3a..9bced34 100644 --- a/src/Api/Handlers/Statuses/DeleteStatusHandler.cs +++ b/src/Api/Handlers/Statuses/DeleteStatusHandler.cs @@ -1,11 +1,11 @@ -// ======================================================= +// ============================================= // Copyright (c) 2026. All rights reserved. // File Name : DeleteStatusHandler.cs // Company : mpaulosky // Author : Matthew Paulosky // Solution Name : IssueManager // Project Name : Api -// ======================================================= +// ============================================= namespace Api.Handlers.Statuses; diff --git a/src/Api/Handlers/Statuses/StatusEndpoints.cs b/src/Api/Handlers/Statuses/StatusEndpoints.cs index 0d19b32..a5d6348 100644 --- a/src/Api/Handlers/Statuses/StatusEndpoints.cs +++ b/src/Api/Handlers/Statuses/StatusEndpoints.cs @@ -70,6 +70,20 @@ public static IEndpointRouteBuilder MapStatusEndpoints(this IEndpointRouteBuilde .Produces(StatusCodes.Status404NotFound) .RequireAuthorization(); + group.MapDelete("{id}", async (string id, DeleteStatusHandler handler) => + { + if (!ObjectId.TryParse(id, out var objectId)) + return Results.BadRequest("Invalid ID format"); + var command = new DeleteStatusCommand { Id = objectId }; + var result = await handler.Handle(command); + return result.Success ? Results.NoContent() : Results.NotFound(); + }) + .WithName("DeleteStatus") + .WithSummary("Delete (archive) a status") + .Produces(StatusCodes.Status204NoContent) + .Produces(StatusCodes.Status404NotFound) + .RequireAuthorization(); + return app; } } diff --git a/src/Shared/Contracts/ListIssuesQuery.cs b/src/Shared/Contracts/ListIssuesQuery.cs index 6b8d842..c0cb71f 100644 --- a/src/Shared/Contracts/ListIssuesQuery.cs +++ b/src/Shared/Contracts/ListIssuesQuery.cs @@ -32,4 +32,14 @@ public record ListIssuesQuery /// Gets or sets the author name for filtering by author. /// public string? AuthorName { get; init; } + + /// + /// Gets or sets the status name for filtering by status. + /// + public string? StatusName { get; init; } + + /// + /// Gets or sets the category name for filtering by category. + /// + public string? CategoryName { get; init; } } diff --git a/src/Web/Components/Features/Issues/IssueApiClient.cs b/src/Web/Components/Features/Issues/IssueApiClient.cs index 0a9e512..5417574 100644 --- a/src/Web/Components/Features/Issues/IssueApiClient.cs +++ b/src/Web/Components/Features/Issues/IssueApiClient.cs @@ -17,8 +17,10 @@ public interface IIssueApiClient /// The number of items per page. /// Optional search term to filter by title or description. /// Optional author name to filter by. + /// Optional status name to filter by. + /// Optional category name to filter by. /// Cancellation token. - Task> GetAllAsync(int page = 1, int pageSize = 20, string? searchTerm = null, string? authorName = null, CancellationToken cancellationToken = default); + Task> GetAllAsync(int page = 1, int pageSize = 20, string? searchTerm = null, string? authorName = null, string? statusName = null, string? categoryName = null, CancellationToken cancellationToken = default); /// Gets an issue by its identifier. Task GetByIdAsync(string id, CancellationToken cancellationToken = default); @@ -45,7 +47,7 @@ public class IssueApiClient : IIssueApiClient public IssueApiClient(HttpClient httpClient) => _httpClient = httpClient; /// - public async Task> GetAllAsync(int page = 1, int pageSize = 20, string? searchTerm = null, string? authorName = null, CancellationToken cancellationToken = default) + public async Task> GetAllAsync(int page = 1, int pageSize = 20, string? searchTerm = null, string? authorName = null, string? statusName = null, string? categoryName = null, CancellationToken cancellationToken = default) { try { @@ -58,6 +60,14 @@ public async Task> GetAllAsync(int page = 1, int pag { url += $"&authorName={Uri.EscapeDataString(authorName)}"; } + if (!string.IsNullOrWhiteSpace(statusName)) + { + url += $"&statusName={Uri.EscapeDataString(statusName)}"; + } + if (!string.IsNullOrWhiteSpace(categoryName)) + { + url += $"&categoryName={Uri.EscapeDataString(categoryName)}"; + } var result = await _httpClient.GetFromJsonAsync>(url, cancellationToken).ConfigureAwait(false); return result ?? PaginatedResponse.Empty; diff --git a/src/Web/Components/Features/Issues/IssuesPage.razor b/src/Web/Components/Features/Issues/IssuesPage.razor index 068efbc..a7f470b 100644 --- a/src/Web/Components/Features/Issues/IssuesPage.razor +++ b/src/Web/Components/Features/Issues/IssuesPage.razor @@ -153,7 +153,7 @@ _currentPage = page; try { - var response = await IssueClient.GetAllAsync(page, 20); + var response = await IssueClient.GetAllAsync(page, 20, _searchTerm, null, _statusFilter, _categoryFilter); _issues = response.Items; _totalPages = response.TotalPages; } diff --git a/tests/Api.Tests.Unit/Endpoints/IssueEndpointsTests.cs b/tests/Api.Tests.Unit/Endpoints/IssueEndpointsTests.cs index 9579141..c0beedf 100644 --- a/tests/Api.Tests.Unit/Endpoints/IssueEndpointsTests.cs +++ b/tests/Api.Tests.Unit/Endpoints/IssueEndpointsTests.cs @@ -39,7 +39,7 @@ public async Task ListIssues_ReturnsOk() // Arrange IReadOnlyList items = []; _factory.IssueRepository - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Result<(IReadOnlyList Items, long Total)>.Ok((items, 0L))); // Act diff --git a/tests/Api.Tests.Unit/Handlers/Issues/ListIssuesHandlerTests.cs b/tests/Api.Tests.Unit/Handlers/Issues/ListIssuesHandlerTests.cs index bbc29d0..3bb55d3 100644 --- a/tests/Api.Tests.Unit/Handlers/Issues/ListIssuesHandlerTests.cs +++ b/tests/Api.Tests.Unit/Handlers/Issues/ListIssuesHandlerTests.cs @@ -33,7 +33,7 @@ public async Task Handle_DefaultPagination_ReturnsFirstPageWithCorrectMetadata() var query = new ListIssuesQuery { Page = 1, PageSize = 20 }; var issues = GenerateIssueDtos(20); - _repository.GetAllAsync(1, 20, null, null, Arg.Any()) + _repository.GetAllAsync(1, 20, null, null, null, null, Arg.Any()) .Returns(((IReadOnlyList)issues, 42L)); // Act @@ -54,7 +54,7 @@ public async Task Handle_SecondPage_ReturnsCorrectItems() var query = new ListIssuesQuery { Page = 2, PageSize = 10 }; var issues = GenerateIssueDtos(10); - _repository.GetAllAsync(2, 10, null, null, Arg.Any()) + _repository.GetAllAsync(2, 10, null, null, null, null, Arg.Any()) .Returns(((IReadOnlyList)issues, 42L)); // Act @@ -75,7 +75,7 @@ public async Task Handle_LastPagePartialItems_ReturnsCorrectCount() var query = new ListIssuesQuery { Page = 3, PageSize = 20 }; var issues = GenerateIssueDtos(2); // Last page has only 2 items - _repository.GetAllAsync(3, 20, null, null, Arg.Any()) + _repository.GetAllAsync(3, 20, null, null, null, null, Arg.Any()) .Returns(((IReadOnlyList)issues, 42L)); // Act @@ -95,7 +95,7 @@ public async Task Handle_EmptyResult_ReturnsEmptyList() // Arrange var query = new ListIssuesQuery { Page = 1, PageSize = 20 }; - _repository.GetAllAsync(1, 20, null, null, Arg.Any()) + _repository.GetAllAsync(1, 20, null, null, null, null, Arg.Any()) .Returns(((IReadOnlyList)new List(), 0L)); // Act @@ -115,7 +115,7 @@ public async Task Handle_PageExceedsTotalPages_ReturnsEmptyList() // Arrange var query = new ListIssuesQuery { Page = 10, PageSize = 20 }; - _repository.GetAllAsync(10, 20, null, null, Arg.Any()) + _repository.GetAllAsync(10, 20, null, null, null, null, Arg.Any()) .Returns(((IReadOnlyList)new List(), 42L)); // Act @@ -176,14 +176,14 @@ public async Task Handle_ExcludesArchivedIssues_ByDefault() var query = new ListIssuesQuery { Page = 1, PageSize = 20 }; var issues = GenerateIssueDtos(10); - _repository.GetAllAsync(1, 20, null, null, Arg.Any()) + _repository.GetAllAsync(1, 20, null, null, null, null, Arg.Any()) .Returns(((IReadOnlyList)issues, 10L)); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert - await _repository.Received(1).GetAllAsync(1, 20, null, null, Arg.Any()); + await _repository.Received(1).GetAllAsync(1, 20, null, null, null, null, Arg.Any()); result.Items.Should().HaveCount(10); } @@ -201,7 +201,7 @@ public async Task Handle_OrdersByCreatedAtDescending_NewestFirst() new(ObjectId.GenerateNewId(), "Issue 1", "Desc", DateTime.UtcNow.AddDays(-3), null, UserDto.Empty, CategoryDto.Empty, StatusDto.Empty, false, UserDto.Empty, false, false), }; - _repository.GetAllAsync(1, 3, null, null, Arg.Any()) + _repository.GetAllAsync(1, 3, null, null, null, null, Arg.Any()) .Returns(((IReadOnlyList)orderedIssues, 3L)); // Act diff --git a/tests/Web.Tests.Bunit/Components/Features/Admin/AdminPageTests.cs b/tests/Web.Tests.Bunit/Components/Features/Admin/AdminPageTests.cs index e9767ec..4c206f4 100644 --- a/tests/Web.Tests.Bunit/Components/Features/Admin/AdminPageTests.cs +++ b/tests/Web.Tests.Bunit/Components/Features/Admin/AdminPageTests.cs @@ -31,7 +31,7 @@ public AdminPageTests() _mockIssueClient = Substitute.For(); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(PaginatedResponse.Empty)); _ctx.Services.AddSingleton(_mockIssueClient); } @@ -81,7 +81,7 @@ public void AdminPage_CallsGetAllAsync_WithPageSize100_OnInitialization() // Assert _mockIssueClient.Received(1) - .GetAllAsync(Arg.Any(), 100, Arg.Any(), Arg.Any(), Arg.Any()); + .GetAllAsync(Arg.Any(), 100, Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); } [Fact] @@ -102,7 +102,7 @@ public void AdminPage_ShowsIssueTitle_WhenApiReturnsPendingIssues() // Arrange var issue = MakePendingIssue("My Pending Bug"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); // Act @@ -118,7 +118,7 @@ public void AdminPage_ShowsAuthorAndCategoryInfo_ForPendingIssue() // Arrange var issue = MakePendingIssue("Info Issue"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); // Act @@ -137,7 +137,7 @@ public void AdminPage_DoesNotShowApprovedIssue_AndShowsEmptyMessage() // Arrange var approvedIssue = MakePendingIssue("Approved Issue") with { ApprovedForRelease = true }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(approvedIssue))); // Act @@ -154,7 +154,7 @@ public void AdminPage_DoesNotShowRejectedIssue_AndShowsEmptyMessage() // Arrange var rejectedIssue = MakePendingIssue("Rejected Issue") with { Rejected = true }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(rejectedIssue))); // Act @@ -172,7 +172,7 @@ public void AdminPage_ShowsApproveAndRejectButtons_ForEachPendingIssue() var issue1 = MakePendingIssue("Issue One"); var issue2 = MakePendingIssue("Issue Two"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue1, issue2))); // Act @@ -194,7 +194,7 @@ public async Task AdminPage_ApproveIssue_CallsUpdateAsync_WithApprovedForRelease var issue = MakePendingIssue("Issue To Approve"); var updatedIssue = issue with { ApprovedForRelease = true }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); _mockIssueClient .UpdateAsync(issue.Id.ToString(), Arg.Any(), Arg.Any()) @@ -220,7 +220,7 @@ public async Task AdminPage_ApproveIssue_RemovesIssueFromList_WhenUpdateSucceeds var issue = MakePendingIssue("Issue To Approve"); var updatedIssue = issue with { ApprovedForRelease = true }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); _mockIssueClient .UpdateAsync(issue.Id.ToString(), Arg.Any(), Arg.Any()) @@ -241,7 +241,7 @@ public async Task AdminPage_ApproveIssue_DoesNotRemoveIssue_WhenUpdateReturnsNul // Arrange var issue = MakePendingIssue("Issue That Stays"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); _mockIssueClient .UpdateAsync(issue.Id.ToString(), Arg.Any(), Arg.Any()) @@ -265,7 +265,7 @@ public async Task AdminPage_RejectIssue_CallsUpdateAsync_WithRejectedTrue() var issue = MakePendingIssue("Issue To Reject"); var updatedIssue = issue with { Rejected = true }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); _mockIssueClient .UpdateAsync(issue.Id.ToString(), Arg.Any(), Arg.Any()) @@ -291,7 +291,7 @@ public async Task AdminPage_RejectIssue_RemovesIssueFromList_WhenUpdateSucceeds( var issue = MakePendingIssue("Issue To Reject"); var updatedIssue = issue with { Rejected = true }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); _mockIssueClient .UpdateAsync(issue.Id.ToString(), Arg.Any(), Arg.Any()) @@ -312,7 +312,7 @@ public async Task AdminPage_RejectIssue_DoesNotRemoveIssue_WhenUpdateReturnsNull // Arrange var issue = MakePendingIssue("Rejection Fails"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); _mockIssueClient .UpdateAsync(issue.Id.ToString(), Arg.Any(), Arg.Any()) @@ -335,7 +335,7 @@ public void AdminPage_EditTitle_ShowsTitleInput_WhenEditButtonClicked() // Arrange var issue = MakePendingIssue("Title To Edit"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); var cut = _ctx.Render(); @@ -353,7 +353,7 @@ public void AdminPage_EditTitle_ClearsDescriptionEditState_WhenTitleEditStarts() // Arrange var issue = MakePendingIssue("Dual Edit Issue"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); var cut = _ctx.Render(); @@ -375,7 +375,7 @@ public async Task AdminPage_SaveTitle_CallsUpdateAsync_WithNewTitle() var issue = MakePendingIssue("Original Title"); var updatedIssue = issue with { Title = "Updated Title" }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); _mockIssueClient .UpdateAsync(issue.Id.ToString(), Arg.Any(), Arg.Any()) @@ -403,7 +403,7 @@ public async Task AdminPage_SaveTitle_HidesTitleInput_AfterSave() var issue = MakePendingIssue("Title To Save"); var updatedIssue = issue with { Title = "Saved Title" }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); _mockIssueClient .UpdateAsync(Arg.Any(), Arg.Any(), Arg.Any()) @@ -426,7 +426,7 @@ public void AdminPage_CancelEditTitle_HidesTitleInput() // Arrange var issue = MakePendingIssue("Title To Cancel"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); var cut = _ctx.Render(); @@ -447,7 +447,7 @@ public void AdminPage_EditDescription_ShowsDescriptionInput_WhenEditButtonClicke // Arrange var issue = MakePendingIssue("Issue", "Description To Edit"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); var cut = _ctx.Render(); @@ -465,7 +465,7 @@ public void AdminPage_EditDescription_ClearsTitleEditState_WhenDescriptionEditSt // Arrange var issue = MakePendingIssue("Clear Title On Desc Edit"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); var cut = _ctx.Render(); @@ -487,7 +487,7 @@ public async Task AdminPage_SaveDescription_CallsUpdateAsync_WithNewDescription( var issue = MakePendingIssue("Issue", "Original Description"); var updatedIssue = issue with { Description = "Updated Description" }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); _mockIssueClient .UpdateAsync(issue.Id.ToString(), Arg.Any(), Arg.Any()) @@ -515,7 +515,7 @@ public async Task AdminPage_SaveDescription_HidesDescriptionInput_AfterSave() var issue = MakePendingIssue("Issue", "Desc To Save"); var updatedIssue = issue with { Description = "Saved Description" }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); _mockIssueClient .UpdateAsync(Arg.Any(), Arg.Any(), Arg.Any()) @@ -538,7 +538,7 @@ public void AdminPage_CancelEditDescription_HidesDescriptionInput() // Arrange var issue = MakePendingIssue("Issue", "Description To Cancel"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); var cut = _ctx.Render(); diff --git a/tests/Web.Tests.Bunit/Components/Features/Issues/IssuesPageTests.cs b/tests/Web.Tests.Bunit/Components/Features/Issues/IssuesPageTests.cs index c847acf..0dba47a 100644 --- a/tests/Web.Tests.Bunit/Components/Features/Issues/IssuesPageTests.cs +++ b/tests/Web.Tests.Bunit/Components/Features/Issues/IssuesPageTests.cs @@ -25,7 +25,7 @@ public IssuesPageTests() { _mockIssueClient = Substitute.For(); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(PaginatedResponse.Empty)); TestContext.Services.AddSingleton(_mockIssueClient); } @@ -111,7 +111,7 @@ public void IssuesPage_ShowsIssueTitle_WhenApiReturnsData() 1, 20); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(response)); // Act @@ -129,7 +129,7 @@ public void IssuesPage_CallsGetAllAsync_OnInitialization() // Assert _mockIssueClient.Received(1) - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); } [Fact] @@ -143,6 +143,6 @@ public async Task IssuesPage_ClearFilters_CallsGetAllAsync_WithPage1() // Assert — once on init, once on clear _ = _mockIssueClient.Received(2) - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); } } diff --git a/tests/Web.Tests.Bunit/Components/Features/Profile/ProfilePageTests.cs b/tests/Web.Tests.Bunit/Components/Features/Profile/ProfilePageTests.cs index 0318469..ada73fa 100644 --- a/tests/Web.Tests.Bunit/Components/Features/Profile/ProfilePageTests.cs +++ b/tests/Web.Tests.Bunit/Components/Features/Profile/ProfilePageTests.cs @@ -31,7 +31,7 @@ public ProfilePageTests() _mockIssueClient = Substitute.For(); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(PaginatedResponse.Empty)); _ctx.Services.AddSingleton(_mockIssueClient); } @@ -81,7 +81,7 @@ public void ProfilePage_OnInitialized_CallsGetAllAsync_WithPageSize200AndAuthorN // Assert _mockIssueClient.Received(1) - .GetAllAsync(Arg.Any(), 200, Arg.Any(), "testuser", Arg.Any()); + .GetAllAsync(Arg.Any(), 200, Arg.Any(), "testuser", Arg.Any(), Arg.Any(), Arg.Any()); } // ─── Issue Filtering ───────────────────────────────────────────────────────── @@ -92,7 +92,7 @@ public void ProfilePage_ShowsApprovedSection_WhenApiReturnsApprovedIssues() // Arrange — approved: ApprovedForRelease=true, Rejected=false, Archived=false var issue = MakeIssue("Approved Issue") with { ApprovedForRelease = true }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); // Act @@ -109,7 +109,7 @@ public void ProfilePage_ShowsPendingSection_WhenApiReturnsPendingIssues() // Arrange — pending: ApprovedForRelease=false, Rejected=false, Archived=false (defaults) var issue = MakeIssue("Pending Issue"); _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); // Act @@ -126,7 +126,7 @@ public void ProfilePage_ShowsRejectedSection_WhenApiReturnsRejectedIssues() // Arrange — rejected: Rejected=true var issue = MakeIssue("Rejected Issue") with { Rejected = true }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); // Act @@ -143,7 +143,7 @@ public void ProfilePage_ExcludesArchivedIssues_FromAllLists() // Arrange — archived: Archived=true, ApprovedForRelease=false, Rejected=false var issue = MakeIssue("Archived Issue") with { Archived = true }; _mockIssueClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(MakeResponse(issue))); // Act @@ -180,7 +180,7 @@ public void ProfilePage_ShowsUserFallback_WhenAuthStateHasNoName() var mockClient = Substitute.For(); mockClient - .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.FromResult(PaginatedResponse.Empty)); ctx.Services.AddSingleton(mockClient);