26ff8d9b4f
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
195 lines
6.9 KiB
C#
195 lines
6.9 KiB
C#
using System.Security.Claims;
|
|
using JdeScoping.Api.Controllers;
|
|
using JdeScoping.Api.Hubs;
|
|
using JdeScoping.Core.Interfaces;
|
|
using JdeScoping.Core.Models;
|
|
using JdeScoping.Core.Models.Enums;
|
|
using JdeScoping.Core.Models.Search;
|
|
using JdeScoping.Core.ViewModels;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using Microsoft.Extensions.Logging;
|
|
using NSubstitute;
|
|
using Shouldly;
|
|
|
|
namespace JdeScoping.Api.Tests.Controllers;
|
|
|
|
public class SearchControllerTests
|
|
{
|
|
private readonly ILotFinderRepository _repository;
|
|
private readonly IHubContext<StatusHub> _hubContext;
|
|
private readonly ILogger<SearchController> _logger;
|
|
private readonly SearchController _controller;
|
|
|
|
public SearchControllerTests()
|
|
{
|
|
_repository = Substitute.For<ILotFinderRepository>();
|
|
_hubContext = Substitute.For<IHubContext<StatusHub>>();
|
|
_logger = Substitute.For<ILogger<SearchController>>();
|
|
_controller = new SearchController(_repository, _hubContext, _logger);
|
|
SetupAuthenticatedUser("testuser");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSearches_ReturnsUserSearches_OrderedByStartDtDescending()
|
|
{
|
|
// Arrange
|
|
var searches = new List<Search>
|
|
{
|
|
new Search { Id = 1, Name = "Search 1", UserName = "testuser", StartDt = DateTime.Now.AddHours(-2) },
|
|
new Search { Id = 2, Name = "Search 2", UserName = "testuser", StartDt = DateTime.Now }
|
|
};
|
|
_repository.GetUserSearchesAsync("testuser", Arg.Any<CancellationToken>())
|
|
.Returns(searches);
|
|
|
|
// Act
|
|
var result = await _controller.GetSearches(CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Result.ShouldBeOfType<OkObjectResult>();
|
|
var okResult = (OkObjectResult)result.Result!;
|
|
var viewModels = okResult.Value.ShouldBeAssignableTo<IEnumerable<SearchViewModel>>()!.ToList();
|
|
viewModels.Count.ShouldBe(2);
|
|
viewModels[0].Id.ShouldBe(2); // Most recent first
|
|
viewModels[1].Id.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateSearch_SavesAndPublishesToSignalR()
|
|
{
|
|
// Arrange
|
|
var viewModel = new SearchViewModel
|
|
{
|
|
Name = "New Search",
|
|
Status = SearchStatus.New,
|
|
Criteria = new SearchCriteria()
|
|
};
|
|
_repository.SubmitSearchAsync(Arg.Any<Search>(), Arg.Any<CancellationToken>())
|
|
.Returns(42);
|
|
|
|
var clientProxy = Substitute.For<IClientProxy>();
|
|
var hubClients = Substitute.For<IHubClients>();
|
|
hubClients.All.Returns(clientProxy);
|
|
_hubContext.Clients.Returns(hubClients);
|
|
|
|
// Act
|
|
var result = await _controller.CreateSearch(viewModel, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Result.ShouldBeOfType<CreatedAtActionResult>();
|
|
var createdResult = (CreatedAtActionResult)result.Result!;
|
|
createdResult.Value.ShouldBe(42);
|
|
|
|
await _repository.Received(1).SubmitSearchAsync(
|
|
Arg.Is<Search>(s => s.Name == "New Search" && s.UserName == "testuser"),
|
|
Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CopySearch_ResetsStatusAndTimestamps()
|
|
{
|
|
// Arrange
|
|
var original = new Search
|
|
{
|
|
Id = 1,
|
|
Name = "Original Search",
|
|
UserName = "otheruser",
|
|
Status = SearchStatus.Ended,
|
|
SubmitDt = DateTime.Now.AddHours(-2),
|
|
StartDt = DateTime.Now.AddHours(-2),
|
|
EndDt = DateTime.Now.AddHours(-1),
|
|
CriteriaJson = "{}"
|
|
};
|
|
_repository.GetSearchAsync(1, Arg.Any<CancellationToken>())
|
|
.Returns(original);
|
|
_repository.SubmitSearchAsync(Arg.Any<Search>(), Arg.Any<CancellationToken>())
|
|
.Returns(42);
|
|
|
|
// Act
|
|
var result = await _controller.CopySearch(1, CancellationToken.None);
|
|
|
|
// Assert - CopySearch returns Ok with a copy, doesn't persist
|
|
var okResult = result.Result.ShouldBeOfType<OkObjectResult>();
|
|
var viewModel = okResult.Value.ShouldBeOfType<SearchViewModel>();
|
|
viewModel.Status.ShouldBe(SearchStatus.New);
|
|
viewModel.UserName.ShouldBe("testuser");
|
|
viewModel.Name.ShouldBe("Original Search");
|
|
viewModel.SubmitDt.ShouldBeNull();
|
|
viewModel.StartDt.ShouldBeNull();
|
|
viewModel.EndDt.ShouldBeNull();
|
|
|
|
// CopySearch does NOT persist - it just returns a copy
|
|
await _repository.DidNotReceive().SubmitSearchAsync(
|
|
Arg.Is<Search>(s =>
|
|
s.Status == SearchStatus.New &&
|
|
s.UserName == "testuser" &&
|
|
s.SubmitDt == null &&
|
|
s.StartDt == null &&
|
|
s.EndDt == null &&
|
|
s.Name == "Original Search"),
|
|
Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetResults_ReturnsFileWithCorrectContentType()
|
|
{
|
|
// Arrange
|
|
var excelData = new byte[] { 1, 2, 3, 4, 5 };
|
|
_repository.GetSearchResultsAsync(1, Arg.Any<CancellationToken>())
|
|
.Returns(excelData);
|
|
|
|
// Act
|
|
var result = await _controller.GetResults(1, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.ShouldBeOfType<FileContentResult>();
|
|
var fileResult = (FileContentResult)result;
|
|
fileResult.ContentType.ShouldBe("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
fileResult.FileDownloadName.ShouldBe("search_results.xlsx");
|
|
fileResult.FileContents.ShouldBe(excelData);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetResults_WhenNoResults_Returns404()
|
|
{
|
|
// Arrange
|
|
_repository.GetSearchResultsAsync(1, Arg.Any<CancellationToken>())
|
|
.Returns((byte[]?)null);
|
|
|
|
// Act
|
|
var result = await _controller.GetResults(1, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.ShouldBeOfType<NotFoundResult>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSearch_WhenNotFound_Returns404()
|
|
{
|
|
// Arrange
|
|
_repository.GetSearchAsync(999, Arg.Any<CancellationToken>())
|
|
.Returns((Search?)null);
|
|
|
|
// Act
|
|
var result = await _controller.GetSearch(999, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Result.ShouldBeOfType<NotFoundResult>();
|
|
}
|
|
|
|
private void SetupAuthenticatedUser(string username)
|
|
{
|
|
var claims = new List<Claim>
|
|
{
|
|
new(ClaimTypes.Name, username),
|
|
new("dn", $"CN={username},DC=example,DC=com")
|
|
};
|
|
var identity = new ClaimsIdentity(claims, "Test");
|
|
var principal = new ClaimsPrincipal(identity);
|
|
|
|
var httpContext = new DefaultHttpContext { User = principal };
|
|
_controller.ControllerContext = new ControllerContext { HttpContext = httpContext };
|
|
}
|
|
}
|