fix(deploy): staleness batch logs one Warning per batch, not one per instance
Claude-Session: https://claude.ai/code/session_01Y5ef34iUUMjmwnMpL9xgmB
This commit is contained in:
+73
-1
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NSubstitute;
|
||||
@@ -38,6 +39,9 @@ public class DeploymentServiceStalenessTests
|
||||
/// <summary>The single session every flatten in a batch must share.</summary>
|
||||
private readonly FlattenSession _session = new(new TemplateGraphWatermark());
|
||||
|
||||
/// <summary>Captures the service's log entries so the per-batch Warning can be asserted.</summary>
|
||||
private readonly CapturingLogger _logger = new();
|
||||
|
||||
/// <summary>
|
||||
/// Builds the service over substituted repository and pipeline seams; every
|
||||
/// other dependency is the real concrete collaborator (as elsewhere in this
|
||||
@@ -73,7 +77,34 @@ public class DeploymentServiceStalenessTests
|
||||
OperationLockTimeout = TimeSpan.FromSeconds(5)
|
||||
}),
|
||||
Options.Create(new CommunicationOptions()),
|
||||
NullLogger<DeploymentService>.Instance);
|
||||
_logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures formatted log entries so the batch's single failure Warning can be
|
||||
/// asserted (mirrors the CapturingLogger used elsewhere in the test suite).
|
||||
/// </summary>
|
||||
private sealed class CapturingLogger : ILogger<DeploymentService>
|
||||
{
|
||||
/// <summary>Every entry logged, in order.</summary>
|
||||
public List<(LogLevel Level, string Message)> Entries { get; } = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Log<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
TState state,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
Entries.Add((logLevel, formatter(state, exception)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Builds a one-attribute flattened config with the given scalar value.</summary>
|
||||
@@ -215,6 +246,47 @@ public class DeploymentServiceStalenessTests
|
||||
Assert.Null(result[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetInstanceStalenessAsync_ManyFlattenFailures_LogsExactlyOneWarning()
|
||||
{
|
||||
// The batch runs for the whole fleet on a cache-TTL cadence, so one broken
|
||||
// shared template fanning out to N instances must NOT produce N Warning
|
||||
// lines per minute: the per-instance detail drops to Debug and the batch
|
||||
// emits a single summary Warning naming the first failure.
|
||||
ArrangeDeployedHashes((1, "sha256:deployed"), (2, "sha256:deployed"));
|
||||
foreach (var id in new[] { 1, 2 })
|
||||
{
|
||||
_pipeline.FlattenAndValidateAsync(
|
||||
id, Arg.Any<CancellationToken>(), Arg.Any<bool>(), Arg.Any<FlattenSession?>())
|
||||
.Returns(Result<FlatteningPipelineResult>.Failure($"broken template {id}"));
|
||||
}
|
||||
|
||||
var result = await _service.GetInstanceStalenessAsync([1, 2]);
|
||||
|
||||
Assert.Null(result[1]);
|
||||
Assert.Null(result[2]);
|
||||
|
||||
var warnings = _logger.Entries.Where(e => e.Level == LogLevel.Warning).ToList();
|
||||
var warning = Assert.Single(warnings);
|
||||
Assert.Contains("2 of 2", warning.Message);
|
||||
Assert.Contains("broken template 1", warning.Message);
|
||||
// Both per-instance details are still available, one level down.
|
||||
Assert.Equal(2, _logger.Entries.Count(e => e.Level == LogLevel.Debug));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetInstanceStalenessAsync_NoFailures_LogsNoWarning()
|
||||
{
|
||||
var config = ScalarConfig("5");
|
||||
var hash = _hasher.ComputeHash(config);
|
||||
ArrangeDeployedHashes((1, hash));
|
||||
ArrangeFlatten(1, config, hash);
|
||||
|
||||
await _service.GetInstanceStalenessAsync([1]);
|
||||
|
||||
Assert.DoesNotContain(_logger.Entries, e => e.Level == LogLevel.Warning);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetInstanceStalenessAsync_InstanceWithoutSnapshot_IsOmitted()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user