Skip to content

Commit b961c69

Browse files
committed
Closes [FEATURE REQ] Dry Run / what-if option
Fixes #754
1 parent bad09a3 commit b961c69

5 files changed

Lines changed: 281 additions & 8 deletions

File tree

src/publisher.tests/Common.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,101 @@ from wasCommitPassed in Gen.Bool
173173
GetLocalFileOperations = () => Common.NoOpFileOperations
174174
};
175175
}
176+
}
177+
178+
internal sealed class IsDryRunTests
179+
{
180+
[Test]
181+
public async Task Returns_false_when_the_setting_is_missing()
182+
{
183+
var gen = from fixture in Fixture.Generate()
184+
select fixture;
185+
186+
await gen.SampleAsync(async fixture =>
187+
{
188+
// Arrange
189+
var isDryRun = fixture.Resolve();
190+
191+
// Act
192+
var enabled = isDryRun();
193+
194+
// Assert
195+
await Assert.That(enabled)
196+
.IsFalse();
197+
});
198+
}
199+
200+
[Test]
201+
public async Task Returns_true_when_the_setting_is_true()
202+
{
203+
var gen = from fixture in Fixture.Generate()
204+
from value in Generator.RandomizeCapitalization("true")
205+
let configuration = Common.ToConfiguration([("DRY_RUN", value)])
206+
select fixture with
207+
{
208+
Configuration = configuration
209+
};
210+
211+
await gen.SampleAsync(async fixture =>
212+
{
213+
// Arrange
214+
var isDryRun = fixture.Resolve();
215+
216+
// Act
217+
var enabled = isDryRun();
218+
219+
// Assert
220+
await Assert.That(enabled)
221+
.IsTrue();
222+
});
223+
}
224+
225+
[Test]
226+
public async Task Returns_false_when_the_setting_is_false()
227+
{
228+
var gen = from fixture in Fixture.Generate()
229+
from value in Generator.RandomizeCapitalization("false")
230+
let configuration = Common.ToConfiguration([("DRY_RUN", value)])
231+
select fixture with
232+
{
233+
Configuration = configuration
234+
};
235+
236+
await gen.SampleAsync(async fixture =>
237+
{
238+
// Arrange
239+
var isDryRun = fixture.Resolve();
240+
241+
// Act
242+
var enabled = isDryRun();
243+
244+
// Assert
245+
await Assert.That(enabled)
246+
.IsFalse();
247+
});
248+
}
249+
250+
private sealed record Fixture
251+
{
252+
public required IConfiguration Configuration { get; init; }
253+
254+
public IsDryRun Resolve()
255+
{
256+
var services = new ServiceCollection();
257+
258+
services.AddSingleton(Configuration)
259+
.AddNullLogger();
260+
261+
using var provider = services.BuildServiceProvider();
262+
263+
return CommonModule.ResolveIsDryRun(provider);
264+
}
265+
266+
public static Gen<Fixture> Generate() =>
267+
from configuration in Generator.Configuration
268+
select new Fixture
269+
{
270+
Configuration = configuration
271+
};
272+
}
176273
}

src/publisher.tests/Resource.cs

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ from dtoOption in Generator.JsonObject.OptionOf()
413413
from fixture in Fixture.Generate()
414414
select (resourceKey, putCalls, fixture with
415415
{
416+
IsDryRun = () => false,
416417
GetDto = async (_, _, _, _) =>
417418
{
418419
await ValueTask.CompletedTask;
@@ -458,6 +459,7 @@ public async Task Dto_resources_without_a_dto_are_not_put()
458459
from fixture in Fixture.Generate()
459460
select (resourceKey, putCalls, fixture with
460461
{
462+
IsDryRun = () => false,
461463
GetDto = async (_, _, _, _) =>
462464
{
463465
await ValueTask.CompletedTask;
@@ -506,6 +508,7 @@ from dto in Generator.JsonObject
506508
from fixture in Fixture.Generate()
507509
select (resourceKey, dto, putApis, putWorkspaceApis, putOthers, fixture with
508510
{
511+
IsDryRun = () => false,
509512
GetDto = async (_, _, _, _) =>
510513
{
511514
await ValueTask.CompletedTask;
@@ -562,8 +565,56 @@ await Assert.That(putOthers)
562565
});
563566
}
564567

568+
[Test]
569+
public async Task Resources_are_not_put_in_dry_run()
570+
{
571+
var gen = from resourceKey in Generator.GenerateResourceKey(resource => resource is IResourceWithDto)
572+
from dto in Generator.JsonObject
573+
let putCalls = new ConcurrentBag<byte>()
574+
from fixture in Fixture.Generate()
575+
select (resourceKey, putCalls, fixture with
576+
{
577+
IsDryRun = () => true,
578+
GetDto = async (_, _, _, _) =>
579+
{
580+
await ValueTask.CompletedTask;
581+
return dto;
582+
},
583+
PutApi = async (_, _, _) =>
584+
{
585+
await ValueTask.CompletedTask;
586+
putCalls.Add(0);
587+
},
588+
PutWorkspaceApi = async (_, _, _, _) =>
589+
{
590+
await ValueTask.CompletedTask;
591+
putCalls.Add(0);
592+
},
593+
PutResourceInApim = async (_, _, _, _, _) =>
594+
{
595+
await ValueTask.CompletedTask;
596+
putCalls.Add(0);
597+
}
598+
});
599+
600+
await gen.SampleAsync(async tuple =>
601+
{
602+
// Arrange
603+
var (resourceKey, putCalls, fixture) = tuple;
604+
var putResource = fixture.Resolve();
605+
606+
// Act
607+
await putResource(resourceKey, CancellationToken);
608+
609+
// Assert that no put methods were called
610+
await Assert.That(putCalls)
611+
.IsEmpty();
612+
});
613+
}
614+
565615
private sealed record Fixture
566616
{
617+
public required IsDryRun IsDryRun { get; init; }
567618
public required GetDto GetDto { get; init; }
568619
public required PutApi PutApi { get; init; }
569620
public required PutWorkspaceApi PutWorkspaceApi { get; init; }
@@ -573,7 +624,8 @@ public PutResource Resolve()
573624
{
574625
var services = new ServiceCollection();
575626

576-
services.AddSingleton(GetDto)
627+
services.AddSingleton(IsDryRun)
628+
.AddSingleton(GetDto)
577629
.AddSingleton(PutApi)
578630
.AddSingleton(PutWorkspaceApi)
579631
.AddSingleton(PutResourceInApim)
@@ -587,8 +639,10 @@ public PutResource Resolve()
587639

588640
public static Gen<Fixture> Generate() =>
589641
from dtoOption in Generator.JsonObject.OptionOf()
642+
from isDryRun in Gen.Bool
590643
select new Fixture
591644
{
645+
IsDryRun = () => isDryRun,
592646
GetDto = async (_, _, _, _) =>
593647
{
594648
await ValueTask.CompletedTask;
@@ -625,6 +679,7 @@ public async Task Proper_delegate_is_called()
625679
from fixture in Fixture.Generate()
626680
select (resourceKey, deletedApis, deletedWorkspaceApis, deletedOthers, fixture with
627681
{
682+
IsDryRun = () => false,
628683
DeleteApi = async (name, _) =>
629684
{
630685
await ValueTask.CompletedTask;
@@ -676,8 +731,50 @@ await Assert.That(deletedOthers)
676731
});
677732
}
678733

734+
[Test]
735+
public async Task Resources_are_not_deleted_in_dry_run()
736+
{
737+
var gen = from resourceKey in Generator.ResourceKey
738+
let deleteCalls = new ConcurrentBag<byte>()
739+
from fixture in Fixture.Generate()
740+
select (resourceKey, deleteCalls, fixture with
741+
{
742+
IsDryRun = () => true,
743+
DeleteApi = async (_, _) =>
744+
{
745+
await ValueTask.CompletedTask;
746+
deleteCalls.Add(0);
747+
},
748+
DeleteWorkspaceApi = async (_, _, _) =>
749+
{
750+
await ValueTask.CompletedTask;
751+
deleteCalls.Add(0);
752+
},
753+
DeleteResourceFromApim = async (_, _, _, _) =>
754+
{
755+
await ValueTask.CompletedTask;
756+
deleteCalls.Add(0);
757+
}
758+
});
759+
760+
await gen.SampleAsync(async tuple =>
761+
{
762+
// Arrange
763+
var (resourceKey, deleteCalls, fixture) = tuple;
764+
var deleteResource = fixture.Resolve();
765+
766+
// Act
767+
await deleteResource(resourceKey, CancellationToken);
768+
769+
// Assert that no delete methods were called
770+
await Assert.That(deleteCalls)
771+
.IsEmpty();
772+
});
773+
}
774+
679775
private sealed record Fixture
680776
{
777+
public required IsDryRun IsDryRun { get; init; }
681778
public required DeleteApi DeleteApi { get; init; }
682779
public required DeleteWorkspaceApi DeleteWorkspaceApi { get; init; }
683780
public required DeleteResourceFromApim DeleteResourceFromApim { get; init; }
@@ -686,7 +783,8 @@ public DeleteResource Resolve()
686783
{
687784
var services = new ServiceCollection();
688785

689-
services.AddSingleton(DeleteApi)
786+
services.AddSingleton(IsDryRun)
787+
.AddSingleton(DeleteApi)
690788
.AddSingleton(DeleteWorkspaceApi)
691789
.AddSingleton(DeleteResourceFromApim)
692790
.AddTestActivitySource()
@@ -698,9 +796,10 @@ public DeleteResource Resolve()
698796
}
699797

700798
public static Gen<Fixture> Generate() =>
701-
from dtoOption in Generator.JsonObject.OptionOf()
799+
from isDryRun in Gen.Bool
702800
select new Fixture
703801
{
802+
IsDryRun = () => isDryRun,
704803
DeleteApi = async (_, _) =>
705804
{
706805
await ValueTask.CompletedTask;

src/publisher/Common.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
using common;
2+
using Microsoft.Extensions.Configuration;
23
using Microsoft.Extensions.DependencyInjection;
34
using Microsoft.Extensions.Hosting;
5+
using Microsoft.Extensions.Logging;
46
using System;
57

68
namespace publisher;
79

810
internal delegate FileOperations GetCurrentFileOperations();
11+
internal delegate bool IsDryRun();
912

1013
internal static class CommonModule
1114
{
@@ -29,4 +32,33 @@ internal static GetCurrentFileOperations ResolveGetCurrentFileOperations(IServic
2932
.IfNone(() => throw new InvalidOperationException("Could not get file operations for the current commit."))
3033
: getLocalFileOperations();
3134
}
35+
36+
public static void ConfigureIsDryRun(IHostApplicationBuilder builder)
37+
{
38+
builder.TryAddSingleton(ResolveIsDryRun);
39+
}
40+
41+
internal static IsDryRun ResolveIsDryRun(IServiceProvider provider)
42+
{
43+
var configuration = provider.GetRequiredService<IConfiguration>();
44+
var logger = provider.GetRequiredService<ILogger>();
45+
46+
var lazy = new Lazy<bool>(() =>
47+
{
48+
var enabled = configuration.GetValue("DRY_RUN")
49+
.Bind(value => bool.TryParse(value, out var result)
50+
? Option.Some(result)
51+
: Option.None)
52+
.IfNone(() => false);
53+
54+
if (enabled)
55+
{
56+
logger.LogWarning("Running in dry-run mode. No changes will be made.");
57+
}
58+
59+
return enabled;
60+
});
61+
62+
return () => lazy.Value;
63+
}
3264
}

0 commit comments

Comments
 (0)