using JdeScoping.Core.Models.Search; using JdeScoping.Core.ViewModels; using JdeScoping.DataAccess.Options; using JdeScoping.DataAccess.Exceptions; using JdeScoping.DataAccess.Interfaces; using JdeScoping.DataAccess.Repositories; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NSubstitute; using NSubstitute.ExceptionExtensions; using Shouldly; using Xunit; namespace JdeScoping.DataAccess.Tests; /// /// Unit tests for LotFinderRepository. /// public class LotFinderRepositoryTests { private readonly IDbConnectionFactory _connectionFactory; private readonly ILogger _logger; private readonly IOptions _options; public LotFinderRepositoryTests() { _connectionFactory = Substitute.For(); _logger = Substitute.For>(); _options = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions { DefaultTimeoutSeconds = 30 }); } #region Constructor Tests [Fact] public void Constructor_NullConnectionFactory_ThrowsArgumentNullException() { // Act & Assert Should.Throw( () => new LotFinderRepository(null!, _logger, _options)) .ParamName.ShouldBe("connectionFactory"); } [Fact] public void Constructor_NullLogger_ThrowsArgumentNullException() { // Act & Assert Should.Throw( () => new LotFinderRepository(_connectionFactory, null!, _options)) .ParamName.ShouldBe("logger"); } [Fact] public void Constructor_NullOptions_ThrowsArgumentNullException() { // Act & Assert Should.Throw( () => new LotFinderRepository(_connectionFactory, _logger, null!)) .ParamName.ShouldBe("options"); } [Fact] public void Constructor_ValidParameters_CreatesInstance() { // Act var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Assert repository.ShouldNotBeNull(); } #endregion #region Connection Exception Handling Tests [Fact] public async Task GetUserSearchesAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.GetUserSearchesAsync("testuser")); ex.QueryName.ShouldBe("SQL_GET_USER_SEARCHES"); ex.Repository.ShouldBe("LotFinderRepository"); } [Fact] public async Task GetQueuedSearchesAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.GetQueuedSearchesAsync()); ex.QueryName.ShouldBe("SQL_GET_QUEUED_SEARCHES"); } [Fact] public async Task GetSearchAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.GetSearchAsync(1)); ex.QueryName.ShouldBe("SQL_GET_SEARCH"); } [Fact] public async Task GetSearchResultsAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.GetSearchResultsAsync(1)); ex.QueryName.ShouldBe("SQL_GET_SEARCH_RESULTS"); } [Fact] public async Task SubmitSearchAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); var search = new Search { UserName = "testuser", Name = "Test Search" }; // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.SubmitSearchAsync(search)); ex.QueryName.ShouldBe(SqlObjects.SubmitSearch); } #endregion #region Reference Data Lookup Exception Handling Tests [Fact] public async Task SearchItemsAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.SearchItemsAsync("test")); ex.QueryName.ShouldBe("SQL_SEARCH_ITEMS"); } [Fact] public async Task LookupItemsAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.LookupItemsAsync(["ITEM001"])); ex.QueryName.ShouldBe("SQL_LOOKUP_ITEMS"); } [Fact] public async Task LookupWorkordersAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.LookupWorkordersAsync([12345])); ex.QueryName.ShouldBe("SQL_LOOKUP_WORKORDERS"); } [Fact] public async Task SearchWorkCentersAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.SearchWorkCentersAsync("test")); ex.QueryName.ShouldBe("SQL_SEARCH_WORK_CENTERS"); } [Fact] public async Task SearchProfitCentersAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.SearchProfitCentersAsync("test")); ex.QueryName.ShouldBe("SQL_SEARCH_PROFIT_CENTERS"); } [Fact] public async Task SearchUsersAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.SearchUsersAsync("test")); ex.QueryName.ShouldBe("SQL_SEARCH_USERS"); } [Fact] public async Task LookupLotsAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); var lots = new List { new LotViewModel { LotNumber = "LOT001", ItemNumber = "ITEM001" } }; // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.LookupLotsAsync(lots)); ex.QueryName.ShouldBe("SQL_LOOKUP_LOTS"); } #endregion #region Data Sync Exception Handling Tests [Fact] public async Task GetLastDataUpdatesAsync_ConnectionFails_ThrowsQueryException() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => await repository.GetLastDataUpdatesAsync()); ex.QueryName.ShouldBe("SQL_GET_LAST_DATA_UPDATES"); } #endregion #region Cancellation Tests [Fact] public async Task GetUserSearchesAsync_CancellationRequested_ThrowsOperationCanceledException() { // Arrange using var cts = new CancellationTokenSource(); cts.Cancel(); _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new OperationCanceledException(cts.Token)); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert await Should.ThrowAsync( async () => await repository.GetUserSearchesAsync("testuser", cts.Token)); } [Fact] public async Task GetQueuedSearchesAsync_CancellationRequested_ThrowsOperationCanceledException() { // Arrange using var cts = new CancellationTokenSource(); cts.Cancel(); _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new OperationCanceledException(cts.Token)); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act & Assert await Should.ThrowAsync( async () => await repository.GetQueuedSearchesAsync(cts.Token)); } #endregion #region Logging Tests [Fact] public async Task GetUserSearchesAsync_ConnectionFails_LogsError() { // Arrange _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); var repository = new LotFinderRepository(_connectionFactory, _logger, _options); // Act try { await repository.GetUserSearchesAsync("testuser"); } catch (QueryException) { // Expected } // Assert - verify logging was called _logger.ReceivedWithAnyArgs(1).Log( LogLevel.Error, Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any>()); } #endregion }