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 CmsRepository. /// public class CmsRepositoryTests { private readonly IDbConnectionFactory _connectionFactory; private readonly ILogger _logger; private readonly IOptions _options; public CmsRepositoryTests() { _connectionFactory = Substitute.For(); _logger = Substitute.For>(); _options = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions { DefaultTimeoutSeconds = 30, MisDataTimeoutSeconds = 60000 }); } #region Constructor Tests [Fact] public void Constructor_NullConnectionFactory_ThrowsArgumentNullException() { // Act & Assert Should.Throw( () => new CmsRepository(null!, _logger, _options)) .ParamName.ShouldBe("connectionFactory"); } [Fact] public void Constructor_NullLogger_ThrowsArgumentNullException() { // Act & Assert Should.Throw( () => new CmsRepository(_connectionFactory, null!, _options)) .ParamName.ShouldBe("logger"); } [Fact] public void Constructor_NullOptions_ThrowsArgumentNullException() { // Act & Assert Should.Throw( () => new CmsRepository(_connectionFactory, _logger, null!)) .ParamName.ShouldBe("options"); } [Fact] public void Constructor_ValidParameters_CreatesInstance() { // Act var repository = new CmsRepository(_connectionFactory, _logger, _options); // Assert repository.ShouldNotBeNull(); } #endregion #region GetMisDataAsync Tests [Fact] public async Task GetMisDataAsync_ConnectionFails_ThrowsConnectionException() { // Arrange _connectionFactory.CreateCmsConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "CMS")); var repository = new CmsRepository(_connectionFactory, _logger, _options); // Act & Assert var ex = await Should.ThrowAsync( async () => { await foreach (var _ in repository.GetMisDataAsync()) { } }); ex.DataSource.ShouldBe("CMS"); } [Fact] public async Task GetMisDataAsync_UsesCmsConnection() { // Arrange _connectionFactory.CreateCmsConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "CMS")); var repository = new CmsRepository(_connectionFactory, _logger, _options); // Act try { await foreach (var _ in repository.GetMisDataAsync()) { } } catch (ConnectionException) { // Expected } // Assert - verify correct connection factory method was called await _connectionFactory.Received(1).CreateCmsConnectionAsync(Arg.Any()); } #endregion #region Cancellation Tests [Fact] public async Task GetMisDataAsync_CancellationRequested_ThrowsOperationCanceledException() { // Arrange using var cts = new CancellationTokenSource(); cts.Cancel(); _connectionFactory.CreateCmsConnectionAsync(Arg.Any()) .ThrowsAsync(new OperationCanceledException(cts.Token)); var repository = new CmsRepository(_connectionFactory, _logger, _options); // Act & Assert await Should.ThrowAsync( async () => { await foreach (var _ in repository.GetMisDataAsync(ct: cts.Token)) { } }); } #endregion #region Incremental Sync Tests [Fact] public async Task GetMisDataAsync_WithLastUpdateDT_UsesFilteredQuery() { // Arrange var lastUpdate = new DateTime(2024, 1, 15, 10, 30, 0); _connectionFactory.CreateCmsConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "CMS")); var repository = new CmsRepository(_connectionFactory, _logger, _options); // Act & Assert - this just verifies the method accepts the parameter await Should.ThrowAsync( async () => { await foreach (var _ in repository.GetMisDataAsync(lastUpdate)) { } }); } [Fact] public async Task GetMisDataAsync_WithoutLastUpdateDT_UsesFullQuery() { // Arrange _connectionFactory.CreateCmsConnectionAsync(Arg.Any()) .ThrowsAsync(new ConnectionException("Test connection error", "CMS")); var repository = new CmsRepository(_connectionFactory, _logger, _options); // Act & Assert await Should.ThrowAsync( async () => { await foreach (var _ in repository.GetMisDataAsync()) { } }); } #endregion #region Timeout Configuration Tests [Fact] public void Constructor_UsesMisDataTimeout() { // Arrange var customOptions = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions { MisDataTimeoutSeconds = 999999 }); // Act var repository = new CmsRepository(_connectionFactory, _logger, customOptions); // Assert repository.ShouldNotBeNull(); // The timeout value is internal, verified through behavior } [Fact] public void Constructor_DefaultMisDataTimeout_Is60000Seconds() { // Arrange var defaultOptions = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions()); // Act var repository = new CmsRepository(_connectionFactory, _logger, defaultOptions); // Assert repository.ShouldNotBeNull(); // Default timeout of 60000 seconds is verified implicitly } #endregion }