using System.Net; using System.Text.Json; using JdeScoping.Client.Models; using JdeScoping.Client.Services; using JdeScoping.Core.ApiContracts; using JdeScoping.Core.ViewModels; using RichardSzalay.MockHttp; using Shouldly; namespace JdeScoping.Client.Tests.Services; public class FileApiClientTests { private readonly MockHttpMessageHandler _mockHttp; private readonly FileApiClient _client; private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; public FileApiClientTests() { _mockHttp = new MockHttpMessageHandler(); var httpClient = new HttpClient(_mockHttp) { BaseAddress = new Uri("http://localhost/") }; _client = new FileApiClient(httpClient); } // Route verification tests - Downloads [Fact] public async Task DownloadWorkOrdersTemplateAsync_CallsCorrectRoute_WithPostMethod() { // Arrange var request = _mockHttp.Expect(HttpMethod.Post, $"http://localhost/{ApiRoutes.FileIO.DownloadWorkOrders}") .Respond("application/octet-stream", new MemoryStream(new byte[] { 1, 2, 3 })); // Act await _client.DownloadWorkOrdersTemplateAsync(); // Assert _mockHttp.GetMatchCount(request).ShouldBe(1); } [Fact] public async Task DownloadItemsTemplateAsync_CallsCorrectRoute() { // Arrange var request = _mockHttp.Expect(HttpMethod.Post, $"http://localhost/{ApiRoutes.FileIO.DownloadItems}") .Respond("application/octet-stream", new MemoryStream(new byte[] { 1, 2, 3 })); // Act await _client.DownloadItemsTemplateAsync(); // Assert _mockHttp.GetMatchCount(request).ShouldBe(1); } [Fact] public async Task DownloadComponentLotsTemplateAsync_CallsCorrectRoute() { // Arrange var request = _mockHttp.Expect(HttpMethod.Post, $"http://localhost/{ApiRoutes.FileIO.DownloadComponentLots}") .Respond("application/octet-stream", new MemoryStream(new byte[] { 1, 2, 3 })); // Act await _client.DownloadComponentLotsTemplateAsync(); // Assert _mockHttp.GetMatchCount(request).ShouldBe(1); } [Fact] public async Task DownloadPartOperationsTemplateAsync_CallsCorrectRoute() { // Arrange var request = _mockHttp.Expect(HttpMethod.Post, $"http://localhost/{ApiRoutes.FileIO.DownloadPartOperations}") .Respond("application/octet-stream", new MemoryStream(new byte[] { 1, 2, 3 })); // Act await _client.DownloadPartOperationsTemplateAsync(); // Assert _mockHttp.GetMatchCount(request).ShouldBe(1); } // Route verification tests - Uploads [Fact] public async Task UploadWorkOrdersAsync_CallsCorrectRoute_WithPostMethod() { // Arrange var response = new FileUploadResult { WasSuccessful = true, Data = Array.Empty() }; var request = _mockHttp.Expect(HttpMethod.Post, $"http://localhost/{ApiRoutes.FileIO.UploadWorkOrders}") .Respond("application/json", JsonSerializer.Serialize(response, _jsonOptions)); using var stream = new MemoryStream(new byte[] { 1, 2, 3 }); // Act await _client.UploadWorkOrdersAsync(stream, "test.xlsx"); // Assert _mockHttp.GetMatchCount(request).ShouldBe(1); } [Fact] public async Task UploadItemsAsync_CallsCorrectRoute() { // Arrange var response = new FileUploadResult { WasSuccessful = true, Data = Array.Empty() }; var request = _mockHttp.Expect(HttpMethod.Post, $"http://localhost/{ApiRoutes.FileIO.UploadItems}") .Respond("application/json", JsonSerializer.Serialize(response, _jsonOptions)); using var stream = new MemoryStream(new byte[] { 1, 2, 3 }); // Act await _client.UploadItemsAsync(stream, "test.xlsx"); // Assert _mockHttp.GetMatchCount(request).ShouldBe(1); } // Success tests - Downloads [Fact] public async Task DownloadWorkOrdersTemplateAsync_Success_ReturnsBytes() { // Arrange var expectedBytes = new byte[] { 0x50, 0x4B, 0x03, 0x04 }; // ZIP header _mockHttp.When(HttpMethod.Post, "*") .Respond("application/octet-stream", new MemoryStream(expectedBytes)); // Act var result = await _client.DownloadWorkOrdersTemplateAsync(); // Assert result.IsSuccess.ShouldBeTrue(); result.Value.ShouldBe(expectedBytes); } [Fact] public async Task DownloadWorkOrdersTemplateAsync_WithExistingData_SendsData() { // Arrange var existingData = new List { new() { WorkOrderNumber = 12345 } }; _mockHttp.When(HttpMethod.Post, "*") .With(req => req.Content != null) // Verify body is sent .Respond("application/octet-stream", new MemoryStream(new byte[] { 1, 2, 3 })); // Act var result = await _client.DownloadWorkOrdersTemplateAsync(existingData); // Assert result.IsSuccess.ShouldBeTrue(); } // Success tests - Uploads [Fact] public async Task UploadWorkOrdersAsync_Success_ReturnsWorkOrderList() { // Arrange var workOrders = new[] { new WorkOrderViewModel { WorkOrderNumber = 12345, ItemNumber = "ITEM001" } }; var response = new FileUploadResult { WasSuccessful = true, Data = workOrders }; _mockHttp.When(HttpMethod.Post, "*") .Respond("application/json", JsonSerializer.Serialize(response, _jsonOptions)); using var stream = new MemoryStream(new byte[] { 1, 2, 3 }); // Act var result = await _client.UploadWorkOrdersAsync(stream, "test.xlsx"); // Assert result.IsSuccess.ShouldBeTrue(); result.Value.Count.ShouldBe(1); result.Value[0].WorkOrderNumber.ShouldBe(12345); } [Fact] public async Task UploadItemsAsync_Success_ReturnsItemList() { // Arrange var items = new[] { new ItemViewModel { ItemNumber = "ITEM1", Description = "Test Item" } }; var response = new FileUploadResult { WasSuccessful = true, Data = items }; _mockHttp.When(HttpMethod.Post, "*") .Respond("application/json", JsonSerializer.Serialize(response, _jsonOptions)); using var stream = new MemoryStream(new byte[] { 1, 2, 3 }); // Act var result = await _client.UploadItemsAsync(stream, "test.xlsx"); // Assert result.IsSuccess.ShouldBeTrue(); result.Value.Count.ShouldBe(1); } // Error tests [Fact] public async Task DownloadWorkOrdersTemplateAsync_404_ReturnsNotFound() { // Arrange _mockHttp.When(HttpMethod.Post, "*") .Respond(HttpStatusCode.NotFound); // Act var result = await _client.DownloadWorkOrdersTemplateAsync(); // Assert result.IsNotFound.ShouldBeTrue(); } [Fact] public async Task UploadWorkOrdersAsync_VerifiesMultipartContentType_AndFilename() { // Arrange - verify multipart structure and filename var response = new FileUploadResult { WasSuccessful = true, Data = Array.Empty() }; _mockHttp.When(HttpMethod.Post, "*") .With(req => { var content = req.Content as MultipartFormDataContent; if (content == null) return false; // Check content type is multipart/form-data var contentType = req.Content?.Headers.ContentType?.MediaType; if (contentType != "multipart/form-data") return false; // Check that filename is passed correctly var contentDisposition = content.First().Headers.ContentDisposition; return contentDisposition?.FileName?.Contains("test.xlsx") == true; }) .Respond("application/json", JsonSerializer.Serialize(response, _jsonOptions)); using var stream = new MemoryStream(new byte[] { 1, 2, 3 }); // Act var result = await _client.UploadWorkOrdersAsync(stream, "test.xlsx"); // Assert result.IsSuccess.ShouldBeTrue(); } [Fact] public async Task UploadWorkOrdersAsync_400_ReturnsValidationError() { // Arrange - use actual ValidationProblemDetails structure var validationProblem = new Microsoft.AspNetCore.Mvc.ValidationProblemDetails { Errors = { ["File"] = new[] { "Invalid file format" } } }; _mockHttp.When(HttpMethod.Post, "*") .Respond(HttpStatusCode.BadRequest, "application/json", JsonSerializer.Serialize(validationProblem, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase })); using var stream = new MemoryStream(new byte[] { 1, 2, 3 }); // Act var result = await _client.UploadWorkOrdersAsync(stream, "test.xlsx"); // Assert result.IsValidationError.ShouldBeTrue(); } }