Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/Wolfgang.Etl.SqlBulkCopy/SqlBulkCopyLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public sealed class SqlBulkCopyLoader<TRecord> : LoaderBase<TRecord, SqlBulkCopy
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="connection"/> is <c>null</c>.
/// </exception>
[ExcludeFromCodeCoverage]
public SqlBulkCopyLoader
(
SqlConnection connection
Expand All @@ -82,6 +83,7 @@ SqlConnection connection
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="connection"/> or <paramref name="logger"/> is <c>null</c>.
/// </exception>
[ExcludeFromCodeCoverage]
public SqlBulkCopyLoader
(
SqlConnection connection,
Expand All @@ -106,6 +108,7 @@ ILogger<SqlBulkCopyLoader<TRecord>> logger
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="connection"/> is <c>null</c>.
/// </exception>
[ExcludeFromCodeCoverage]
public SqlBulkCopyLoader
(
SqlConnection connection,
Expand All @@ -128,17 +131,18 @@ public SqlBulkCopyLoader
/// </summary>
/// <param name="wrapperFactory">The factory for creating bulk copy wrappers.</param>
/// <param name="logger">An optional logger instance.</param>
/// <param name="timer">The progress timer to inject.</param>
/// <param name="timer">An optional progress timer to inject. When <c>null</c>, the
/// base class creates a <c>SystemProgressTimer</c>.</param>
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;
}

Expand Down Expand Up @@ -657,6 +661,7 @@ private void EnsureConnectionAvailable(string operation)



[ExcludeFromCodeCoverage]
private ISqlBulkCopyWrapperFactory CreateFactory()
{
EnsureConnectionAvailable("bulk copy");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestRecord>(factory, logger: null, timer: null);
var captured = new List<SqlBulkCopyReport>();
var progress = new SynchronousProgress<SqlBulkCopyReport>(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<ArgumentOutOfRangeException>
(
() => 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<ArgumentOutOfRangeException>
(
() => 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<NotMappedWithChildrenRecord>(factory, logger: null, timer)
{
PreAction = PreAction.DeleteAllRecords
};

var items = new[]
{
new NotMappedWithChildrenRecord { Id = 1 }
};

await Assert.ThrowsAsync<InvalidOperationException>
(
() => 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<NotMappedWithChildrenRecord>(factory, logger: null, timer)
{
PreAction = PreAction.TruncateTable
};

var items = new[]
{
new NotMappedWithChildrenRecord { Id = 1 }
};

await Assert.ThrowsAsync<InvalidOperationException>
(
() => sut.LoadAsync(ToAsyncEnumerableAsync(items))
);
}



// --- EnableDataValidation false path ---

[Fact]
Expand Down