From 76ce35458c58ba483b0db2422f2fc6229bf9bb1a Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 6 Jan 2026 11:27:33 -0500 Subject: [PATCH] test: add SearchApiClientTests with route and success/error tests Add comprehensive unit tests for SearchApiClient covering: - Route verification tests for all 6 methods (GetUserSearches, GetQueuedSearches, GetSearch, CopySearch, CreateSearch, GetResults) - Success tests verifying deserialization for all methods - Representative error tests (404 for GetSearch, 401 for GetUserSearches) Uses MockHttpMessageHandler to verify correct API routes are called and ApiResult pattern maps responses correctly. --- .../Services/SearchApiClientTests.cs | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 NEW/tests/JdeScoping.Client.Tests/Services/SearchApiClientTests.cs diff --git a/NEW/tests/JdeScoping.Client.Tests/Services/SearchApiClientTests.cs b/NEW/tests/JdeScoping.Client.Tests/Services/SearchApiClientTests.cs new file mode 100644 index 0000000..90d382c --- /dev/null +++ b/NEW/tests/JdeScoping.Client.Tests/Services/SearchApiClientTests.cs @@ -0,0 +1,253 @@ +using System.Net; +using System.Text.Json; +using JdeScoping.Client.Services; +using JdeScoping.Core.ApiContracts; +using JdeScoping.Core.ViewModels; +using RichardSzalay.MockHttp; +using Shouldly; + +namespace JdeScoping.Client.Tests.Services; + +public class SearchApiClientTests +{ + private readonly MockHttpMessageHandler _mockHttp; + private readonly SearchApiClient _client; + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + public SearchApiClientTests() + { + _mockHttp = new MockHttpMessageHandler(); + var httpClient = new HttpClient(_mockHttp) { BaseAddress = new Uri("http://localhost/") }; + _client = new SearchApiClient(httpClient); + } + + // Route verification tests + + [Fact] + public async Task GetUserSearchesAsync_CallsCorrectRoute() + { + // Arrange + var request = _mockHttp.Expect(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.Base}") + .Respond("application/json", "[]"); + + // Act + await _client.GetUserSearchesAsync(); + + // Assert + _mockHttp.GetMatchCount(request).ShouldBe(1); + } + + [Fact] + public async Task GetQueuedSearchesAsync_CallsCorrectRoute() + { + // Arrange + var request = _mockHttp.Expect(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.Queue}") + .Respond("application/json", "[]"); + + // Act + await _client.GetQueuedSearchesAsync(); + + // Assert + _mockHttp.GetMatchCount(request).ShouldBe(1); + } + + [Fact] + public async Task GetSearchAsync_CallsCorrectRoute() + { + // Arrange + var searchId = 42; + var request = _mockHttp.Expect(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.GetById(searchId)}") + .Respond("application/json", JsonSerializer.Serialize(CreateTestSearch(searchId), JsonOptions)); + + // Act + await _client.GetSearchAsync(searchId); + + // Assert + _mockHttp.GetMatchCount(request).ShouldBe(1); + } + + [Fact] + public async Task CreateSearchAsync_CallsCorrectRoute_WithPostMethod() + { + // Arrange + var request = _mockHttp.Expect(HttpMethod.Post, $"http://localhost/{ApiRoutes.Search.Base}") + .Respond("application/json", "123"); + + // Act + await _client.CreateSearchAsync(CreateTestSearch(0)); + + // Assert + _mockHttp.GetMatchCount(request).ShouldBe(1); + } + + [Fact] + public async Task GetResultsAsync_CallsCorrectRoute() + { + // Arrange + var searchId = 42; + var request = _mockHttp.Expect(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.GetResults(searchId)}") + .Respond("application/octet-stream", new MemoryStream(new byte[] { 1, 2, 3 })); + + // Act + await _client.GetResultsAsync(searchId); + + // Assert + _mockHttp.GetMatchCount(request).ShouldBe(1); + } + + [Fact] + public async Task CopySearchAsync_CallsCorrectRoute() + { + // Arrange + var searchId = 42; + var request = _mockHttp.Expect(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.GetCopy(searchId)}") + .Respond("application/json", JsonSerializer.Serialize(CreateTestSearch(100), JsonOptions)); + + // Act + await _client.CopySearchAsync(searchId); + + // Assert + _mockHttp.GetMatchCount(request).ShouldBe(1); + } + + // Success tests + + [Fact] + public async Task GetUserSearchesAsync_Success_ReturnsSearchList() + { + // Arrange + var searches = new List { CreateTestSearch(1), CreateTestSearch(2) }; + _mockHttp.When(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.Base}") + .Respond("application/json", JsonSerializer.Serialize(searches, JsonOptions)); + + // Act + var result = await _client.GetUserSearchesAsync(); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.Count.ShouldBe(2); + result.Value[0].Id.ShouldBe(1); + } + + [Fact] + public async Task GetSearchAsync_Success_ReturnsSearch() + { + // Arrange + var search = CreateTestSearch(42); + _mockHttp.When(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.GetById(42)}") + .Respond("application/json", JsonSerializer.Serialize(search, JsonOptions)); + + // Act + var result = await _client.GetSearchAsync(42); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.Id.ShouldBe(42); + } + + [Fact] + public async Task CreateSearchAsync_Success_ReturnsId() + { + // Arrange + _mockHttp.When(HttpMethod.Post, $"http://localhost/{ApiRoutes.Search.Base}") + .Respond("application/json", "123"); + + // Act + var result = await _client.CreateSearchAsync(CreateTestSearch(0)); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(123); + } + + [Fact] + public async Task GetQueuedSearchesAsync_Success_ReturnsSearchList() + { + // Arrange + var searches = new List { CreateTestSearch(1) }; + _mockHttp.When(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.Queue}") + .Respond("application/json", JsonSerializer.Serialize(searches, JsonOptions)); + + // Act + var result = await _client.GetQueuedSearchesAsync(); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.Count.ShouldBe(1); + } + + [Fact] + public async Task GetResultsAsync_Success_ReturnsBytes() + { + // Arrange - GetResultsAsync returns byte[] for Excel file + var expectedBytes = new byte[] { 0x50, 0x4B, 0x03, 0x04 }; // ZIP/XLSX header + _mockHttp.When(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.GetResults(42)}") + .Respond("application/octet-stream", new MemoryStream(expectedBytes)); + + // Act + var result = await _client.GetResultsAsync(42); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.ShouldBe(expectedBytes); + } + + [Fact] + public async Task CopySearchAsync_Success_ReturnsCopiedSearch() + { + // Arrange + var copiedSearch = CreateTestSearch(100); + copiedSearch.Name = "Copy of Search 42"; + _mockHttp.When(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.GetCopy(42)}") + .Respond("application/json", JsonSerializer.Serialize(copiedSearch, JsonOptions)); + + // Act + var result = await _client.CopySearchAsync(42); + + // Assert + result.IsSuccess.ShouldBeTrue(); + result.Value.Id.ShouldBe(100); + result.Value.Name.ShouldBe("Copy of Search 42"); + } + + // Representative error tests + + [Fact] + public async Task GetSearchAsync_404_ReturnsNotFound() + { + // Arrange + _mockHttp.When(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.GetById(999)}") + .Respond(HttpStatusCode.NotFound); + + // Act + var result = await _client.GetSearchAsync(999); + + // Assert + result.IsNotFound.ShouldBeTrue(); + } + + [Fact] + public async Task GetUserSearchesAsync_401_ReturnsUnauthorized() + { + // Arrange + _mockHttp.When(HttpMethod.Get, $"http://localhost/{ApiRoutes.Search.Base}") + .Respond(HttpStatusCode.Unauthorized); + + // Act + var result = await _client.GetUserSearchesAsync(); + + // Assert + result.IsUnauthorized.ShouldBeTrue(); + } + + private static SearchViewModel CreateTestSearch(int id) => new() + { + Id = id, + Name = $"Search {id}", + UserName = "testuser", + Status = Core.Models.Enums.SearchStatus.New + }; +}