diff --git a/src/Wolfgang.Etl.SqlBulkCopy/SqlBulkCopyLoader.cs b/src/Wolfgang.Etl.SqlBulkCopy/SqlBulkCopyLoader.cs index be5a273..5fed3fb 100644 --- a/src/Wolfgang.Etl.SqlBulkCopy/SqlBulkCopyLoader.cs +++ b/src/Wolfgang.Etl.SqlBulkCopy/SqlBulkCopyLoader.cs @@ -61,6 +61,7 @@ public sealed class SqlBulkCopyLoader : LoaderBase /// Thrown when is null. /// + [ExcludeFromCodeCoverage] public SqlBulkCopyLoader ( SqlConnection connection @@ -82,6 +83,7 @@ SqlConnection connection /// /// Thrown when or is null. /// + [ExcludeFromCodeCoverage] public SqlBulkCopyLoader ( SqlConnection connection, @@ -106,6 +108,7 @@ ILogger> logger /// /// Thrown when is null. /// + [ExcludeFromCodeCoverage] public SqlBulkCopyLoader ( SqlConnection connection, @@ -128,17 +131,18 @@ public SqlBulkCopyLoader /// /// The factory for creating bulk copy wrappers. /// An optional logger instance. - /// The progress timer to inject. + /// An optional progress timer to inject. When null, the + /// base class creates a SystemProgressTimer. internal SqlBulkCopyLoader ( ISqlBulkCopyWrapperFactory wrapperFactory, ILogger? logger, - IProgressTimer timer + IProgressTimer? timer ) { _wrapperFactory = wrapperFactory ?? throw new ArgumentNullException(nameof(wrapperFactory)); _logger = logger ?? NullLogger.Instance; - _progressTimer = timer ?? throw new ArgumentNullException(nameof(timer)); + _progressTimer = timer; _options = SqlBulkCopyOptions.Default; } @@ -657,6 +661,7 @@ private void EnsureConnectionAvailable(string operation) + [ExcludeFromCodeCoverage] private ISqlBulkCopyWrapperFactory CreateFactory() { EnsureConnectionAvailable("bulk copy"); diff --git a/tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/SqlBulkCopyLoaderTests.cs b/tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/SqlBulkCopyLoaderTests.cs index 87241b9..ec9cc3c 100644 --- a/tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/SqlBulkCopyLoaderTests.cs +++ b/tests/Wolfgang.Etl.SqlBulkCopy.Tests.Unit/SqlBulkCopyLoaderTests.cs @@ -559,6 +559,102 @@ public async Task LoadAsync_with_progress_reports_batch_count_Async() + // --- CreateProgressTimer fallback path test --- + + [Fact] + public async Task LoadAsync_with_progress_when_no_timer_injected_uses_base_timer_Async() + { + var factory = new FakeSqlBulkCopyWrapperFactory(); + var sut = new SqlBulkCopyLoader(factory, logger: null, timer: null); + var captured = new List(); + var progress = new SynchronousProgress(captured.Add); + + await sut.LoadAsync(ToAsyncEnumerableAsync(CreateTestItems(3)), progress); + + // The fact that LoadAsync completed without throwing means + // base.CreateProgressTimer(progress) was successfully invoked + // and the resulting SystemProgressTimer was started and stopped. + Assert.Equal(3, sut.CurrentItemCount); + } + + + + // --- Invalid enum value tests --- + + [Fact] + public Task LoadAsync_when_PreAction_is_invalid_enum_value_throws_Async() + { + var sut = CreateSut(); + sut.PreAction = (PreAction)999; + + return Assert.ThrowsAsync + ( + () => sut.LoadAsync(ToAsyncEnumerableAsync(CreateTestItems(1))) + ); + } + + + + [Fact] + public Task LoadAsync_when_PostAction_is_invalid_enum_value_throws_Async() + { + var sut = CreateSut(); + sut.PostAction = (PostAction)999; + + return Assert.ThrowsAsync + ( + () => sut.LoadAsync(ToAsyncEnumerableAsync(CreateTestItems(1))) + ); + } + + + + [Fact] + public async Task LoadAsync_when_PreAction_DeleteAllRecords_with_NotMapped_type_throws_Async() + { + var factory = new FakeSqlBulkCopyWrapperFactory(); + var timer = new ManualProgressTimer(); + var sut = new SqlBulkCopyLoader(factory, logger: null, timer) + { + PreAction = PreAction.DeleteAllRecords + }; + + var items = new[] + { + new NotMappedWithChildrenRecord { Id = 1 } + }; + + await Assert.ThrowsAsync + ( + () => sut.LoadAsync(ToAsyncEnumerableAsync(items)) + ); + } + + + + [Fact] + public async Task LoadAsync_when_PreAction_TruncateTable_with_NotMapped_type_throws_Async() + { + var factory = new FakeSqlBulkCopyWrapperFactory(); + var timer = new ManualProgressTimer(); + var sut = new SqlBulkCopyLoader(factory, logger: null, timer) + { + PreAction = PreAction.TruncateTable + }; + + var items = new[] + { + new NotMappedWithChildrenRecord { Id = 1 } + }; + + await Assert.ThrowsAsync + ( + () => sut.LoadAsync(ToAsyncEnumerableAsync(items)) + ); + } + + + // --- EnableDataValidation false path --- [Fact]