- Install CsvHelper 33.1.0 and Microsoft.Graph 5.74.0 (main + test projects) - Add 14 core model/enum files (BulkOperationResult, BulkMemberRow, BulkSiteRow, TransferJob, FolderStructureRow, SiteTemplate, SiteTemplateOptions, TemplateLibraryInfo, TemplateFolderInfo, TemplatePermissionGroup, ConflictPolicy, TransferMode, CsvValidationRow) - Add 6 service interfaces (IFileTransferService, IBulkMemberService, IBulkSiteService, ITemplateService, IFolderStructureService, ICsvValidationService) - Add BulkOperationRunner with continue-on-error and cancellation support - Add BulkResultCsvExportService stub (compile-ready) - Add test scaffolds: BulkOperationRunnerTests (5 passing), BulkResultCsvExportServiceTests (2 passing), CsvValidationServiceTests (6 skipped), TemplateRepositoryTests (4 skipped)
106 lines
3.4 KiB
C#
106 lines
3.4 KiB
C#
using SharepointToolbox.Core.Models;
|
|
using SharepointToolbox.Services;
|
|
|
|
namespace SharepointToolbox.Tests.Services;
|
|
|
|
public class BulkOperationRunnerTests
|
|
{
|
|
[Fact]
|
|
public async Task RunAsync_AllSucceed_ReturnsAllSuccess()
|
|
{
|
|
var items = new List<string> { "a", "b", "c" };
|
|
var progress = new Progress<OperationProgress>();
|
|
|
|
var summary = await BulkOperationRunner.RunAsync(
|
|
items,
|
|
(item, idx, ct) => Task.CompletedTask,
|
|
progress,
|
|
CancellationToken.None);
|
|
|
|
Assert.Equal(3, summary.TotalCount);
|
|
Assert.Equal(3, summary.SuccessCount);
|
|
Assert.Equal(0, summary.FailedCount);
|
|
Assert.False(summary.HasFailures);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RunAsync_SomeItemsFail_ContinuesAndReportsPerItem()
|
|
{
|
|
var items = new List<string> { "ok1", "fail", "ok2" };
|
|
var progress = new Progress<OperationProgress>();
|
|
|
|
var summary = await BulkOperationRunner.RunAsync(
|
|
items,
|
|
(item, idx, ct) =>
|
|
{
|
|
if (item == "fail") throw new InvalidOperationException("Test error");
|
|
return Task.CompletedTask;
|
|
},
|
|
progress,
|
|
CancellationToken.None);
|
|
|
|
Assert.Equal(3, summary.TotalCount);
|
|
Assert.Equal(2, summary.SuccessCount);
|
|
Assert.Equal(1, summary.FailedCount);
|
|
Assert.True(summary.HasFailures);
|
|
Assert.Contains(summary.FailedItems, r => r.ErrorMessage == "Test error");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RunAsync_Cancelled_ThrowsOperationCanceled()
|
|
{
|
|
var items = new List<string> { "a", "b", "c" };
|
|
var cts = new CancellationTokenSource();
|
|
cts.Cancel();
|
|
var progress = new Progress<OperationProgress>();
|
|
|
|
await Assert.ThrowsAsync<OperationCanceledException>(() =>
|
|
BulkOperationRunner.RunAsync(
|
|
items,
|
|
(item, idx, ct) => Task.CompletedTask,
|
|
progress,
|
|
cts.Token));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RunAsync_CancelledMidOperation_StopsProcessing()
|
|
{
|
|
var items = new List<string> { "a", "b", "c", "d" };
|
|
var cts = new CancellationTokenSource();
|
|
var processedCount = 0;
|
|
var progress = new Progress<OperationProgress>();
|
|
|
|
await Assert.ThrowsAsync<OperationCanceledException>(() =>
|
|
BulkOperationRunner.RunAsync(
|
|
items,
|
|
async (item, idx, ct) =>
|
|
{
|
|
Interlocked.Increment(ref processedCount);
|
|
if (idx == 1) cts.Cancel(); // cancel after second item
|
|
await Task.CompletedTask;
|
|
},
|
|
progress,
|
|
cts.Token));
|
|
|
|
Assert.True(processedCount <= 3); // should not process all 4
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RunAsync_ReportsProgress()
|
|
{
|
|
var items = new List<string> { "a", "b" };
|
|
var progressReports = new List<OperationProgress>();
|
|
var progress = new Progress<OperationProgress>(p => progressReports.Add(p));
|
|
|
|
await BulkOperationRunner.RunAsync(
|
|
items,
|
|
(item, idx, ct) => Task.CompletedTask,
|
|
progress,
|
|
CancellationToken.None);
|
|
|
|
// Progress is async, give it a moment to flush
|
|
await Task.Delay(100);
|
|
Assert.True(progressReports.Count >= 2);
|
|
}
|
|
}
|