using JdeScoping.Core.Interfaces;
using JdeScoping.Core.Models.Enums;
using JdeScoping.Core.Models.Search;
using JdeScoping.Core.Models.SearchResults;
using JdeScoping.DataSync.Contracts;
using JdeScoping.DataSync.Options;
using JdeScoping.DataSync.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Shouldly;
using MsOptions = Microsoft.Extensions.Options.Options;
namespace JdeScoping.DataSync.Tests.Services;
///
/// Unit tests for SearchExecutionService.
/// Tests cancellation handling, error paths, and success scenarios.
///
public class SearchExecutionServiceTests
{
private readonly ISearchRepository _searchRepository;
private readonly ISearchProcessor _searchProcessor;
private readonly IExcelExportService _excelExportService;
private readonly ISearchNotificationService _notificationService;
private readonly IOptions _options;
private readonly ILogger _logger;
public SearchExecutionServiceTests()
{
_searchRepository = Substitute.For();
_searchProcessor = Substitute.For();
_excelExportService = Substitute.For();
_notificationService = Substitute.For();
_options = MsOptions.Create(new WorkProcessorOptions { SearchTimeout = TimeSpan.FromSeconds(30) });
_logger = Substitute.For>();
}
#region Constructor Tests
[Fact]
public void Constructor_WithNullSearchRepository_ThrowsArgumentNullException()
{
// Act & Assert
Should.Throw(() =>
new SearchExecutionService(
null!,
_searchProcessor,
_excelExportService,
_notificationService,
_options,
_logger));
}
[Fact]
public void Constructor_WithNullSearchProcessor_ThrowsArgumentNullException()
{
// Act & Assert
Should.Throw(() =>
new SearchExecutionService(
_searchRepository,
null!,
_excelExportService,
_notificationService,
_options,
_logger));
}
[Fact]
public void Constructor_WithNullExcelExportService_ThrowsArgumentNullException()
{
// Act & Assert
Should.Throw(() =>
new SearchExecutionService(
_searchRepository,
_searchProcessor,
null!,
_notificationService,
_options,
_logger));
}
[Fact]
public void Constructor_WithNullNotificationService_ThrowsArgumentNullException()
{
// Act & Assert
Should.Throw(() =>
new SearchExecutionService(
_searchRepository,
_searchProcessor,
_excelExportService,
null!,
_options,
_logger));
}
[Fact]
public void Constructor_WithNullOptions_ThrowsArgumentNullException()
{
// Act & Assert
Should.Throw(() =>
new SearchExecutionService(
_searchRepository,
_searchProcessor,
_excelExportService,
_notificationService,
null!,
_logger));
}
[Fact]
public void Constructor_WithNullLogger_ThrowsArgumentNullException()
{
// Act & Assert
Should.Throw(() =>
new SearchExecutionService(
_searchRepository,
_searchProcessor,
_excelExportService,
_notificationService,
_options,
null!));
}
[Fact]
public void Constructor_WithValidDependencies_CreatesInstance()
{
// Act
var sut = CreateService();
// Assert
sut.ShouldNotBeNull();
}
#endregion
#region ExecuteSearchAsync Success Path
[Fact]
public async Task ExecuteSearchAsync_Success_StartsSearch()
{
// Arrange
var search = new Search { Id = 1, Status = SearchStatus.Queued };
var model = new SearchModel { Id = 1, Results = [] };
var excelBytes = new byte[] { 1, 2, 3 };
_searchProcessor.ExecuteSearchToModelAsync(Arg.Any(), Arg.Any())
.Returns(model);
_excelExportService.GenerateAsync(Arg.Any